diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 20d67ea3c1f2..125528a12431 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -43,5 +43,5 @@ Please paste the stack trace here if available. ``` diff --git a/README.md b/README.md index bc195a5a8b8f..38ccfdb15dec 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,13 @@ standards for C# and the Common Language Runtime. The Mono project is part of the [.NET Foundation](https://www.dotnetfoundation.org/) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mono/mono?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +Join us on [Discord](https://aka.ms/dotnet-discord) in the `#monovm` channel: + + + + + +### Contents 1. [Compilation and Installation](#compilation-and-installation) 2. [Using Mono](#using-mono) diff --git a/configure.ac b/configure.ac index d783e536e82f..0a8c0ba63a17 100644 --- a/configure.ac +++ b/configure.ac @@ -890,7 +890,7 @@ AC_ARG_ENABLE(visibility-hidden, WARN='' if test x"$GCC" = xyes; then WARN='-Wall -Wunused -Wmissing-declarations -Wpointer-arith -Wno-cast-qual -Wwrite-strings -Wno-switch -Wno-switch-enum -Wno-unused-value -Wno-attributes' - CFLAGS="$CFLAGS -Wmissing-prototypes -Wstrict-prototypes -Wnested-externs -Wno-format-zero-length" + CFLAGS="$CFLAGS -Wmissing-prototypes -Wstrict-prototypes -Wnested-externs -Wno-format-zero-length -Wc++-compat" # We require C99 with some GNU extensions, e.g. `linux` macro CFLAGS="$CFLAGS -std=gnu99" @@ -1420,7 +1420,7 @@ if test x$with_runtime_preset = xnetcore; then mono_feature_disable_perfcounters='yes' mono_feature_disable_attach='yes' mono_feature_disable_cfgdir_config='yes' - if test "x$enable_monodroid" = "x" -a "x$enable_monotouch" = "x"; then + if test "x$enable_monodroid" = "xno" -a "x$enable_monotouch" = "xno"; then mono_feature_disable_dllmap='yes' # FIXME: the mobile products use this fi disable_mono_native=yes diff --git a/external/corefx b/external/corefx index b9e66087d6a9..7c24bb0756fd 160000 --- a/external/corefx +++ b/external/corefx @@ -1 +1 @@ -Subproject commit b9e66087d6a9d4eb3a30c80ead3b8402510dad3d +Subproject commit 7c24bb0756fd39fbf09b8777f25c15d21d78eb46 diff --git a/mcs/class/I18N/CJK/GB18030Encoding.cs b/mcs/class/I18N/CJK/GB18030Encoding.cs index f62121e81f04..6f21c2534756 100644 --- a/mcs/class/I18N/CJK/GB18030Encoding.cs +++ b/mcs/class/I18N/CJK/GB18030Encoding.cs @@ -430,10 +430,9 @@ public unsafe override int GetBytesImpl (char* chars, int charCount, byte* bytes } #else - public override int GetByteCount(char[] chars, int index, int count, bool refresh) + public override int GetByteCount(char[] chars, int start, int count, bool refresh) { - int start = 0; - int end = count; + int end = start + count; int ret = 0; while (start < end) { diff --git a/mcs/class/Mono.Debugger.Soft/Test/dtest-app.cs b/mcs/class/Mono.Debugger.Soft/Test/dtest-app.cs index f61919f0a386..db17abaeb8d6 100644 --- a/mcs/class/Mono.Debugger.Soft/Test/dtest-app.cs +++ b/mcs/class/Mono.Debugger.Soft/Test/dtest-app.cs @@ -471,6 +471,14 @@ public class Tests : TestsBase, ITest2 #pragma warning restore 0414 + public string BreakInField + { + get { + Debugger.Break (); + return "Foo"; + } + } + public class NestedClass { } diff --git a/mcs/class/Mono.Debugger.Soft/Test/dtest.cs b/mcs/class/Mono.Debugger.Soft/Test/dtest.cs index e717d1f676e2..e6ec1dd62967 100644 --- a/mcs/class/Mono.Debugger.Soft/Test/dtest.cs +++ b/mcs/class/Mono.Debugger.Soft/Test/dtest.cs @@ -1806,7 +1806,7 @@ public void TypeInfo () { t = frame.Method.GetParameters ()[7].ParameterType; var props = t.GetProperties (); - Assert.AreEqual (3, props.Length); + Assert.AreEqual (4, props.Length); foreach (PropertyInfoMirror prop in props) { ParameterInfoMirror[] indexes = prop.GetIndexParameters (); @@ -4953,6 +4953,23 @@ public void IfPropertyStepping () { Assert.IsTrue ((e as StepEvent).Method.Name == "op_Equality" || (e as StepEvent).Method.Name == "if_property_stepping"); } + [Test] + public void DebuggerBreakInFieldDoesNotHang () { + vm.EnableEvents (EventType.UserBreak); + Event e = run_until ("o1"); + + StackFrame frame = e.Thread.GetFrames () [0]; + object val = frame.GetThis (); + Assert.IsTrue (val is ObjectMirror); + Assert.AreEqual ("Tests", (val as ObjectMirror).Type.Name); + ObjectMirror o = (val as ObjectMirror); + TypeMirror t = o.Type; + + MethodMirror m = t.GetProperty ("BreakInField").GetGetMethod(); + Value v = o.InvokeMethod (e.Thread, m, null, InvokeOptions.DisableBreakpoints); + AssertValue ("Foo", v); + } + #if !MONODROID_TOOLS void HandleEvents () { diff --git a/mcs/class/System.Windows.Forms/System.Windows.Forms/GridEntry.cs b/mcs/class/System.Windows.Forms/System.Windows.Forms/GridEntry.cs index 063b89f375c4..8cc5581c3751 100644 --- a/mcs/class/System.Windows.Forms/System.Windows.Forms/GridEntry.cs +++ b/mcs/class/System.Windows.Forms/System.Windows.Forms/GridEntry.cs @@ -466,8 +466,7 @@ public bool SetValue (object value, out string error) if (this.IsReadOnly) return false; - if (value != Value) { - SetValueCore(value, out error); + if (SetValueCore (value, out error)) { InvalidateChildGridItemsCache (); property_grid.OnPropertyValueChangedInternal (this, this.Value); return true; @@ -478,6 +477,40 @@ public bool SetValue (object value, out string error) protected virtual bool SetValueCore (object value, out string error) { error = null; + + TypeConverter converter = GetConverter (); + Type valueType = value != null ? value.GetType () : null; + // if the new value is not of the same type try to convert it + if (valueType != null && this.PropertyDescriptor.PropertyType != null && + !this.PropertyDescriptor.PropertyType.IsAssignableFrom (valueType)) { + bool conversionError = false; + try { + if (converter != null && + converter.CanConvertFrom ((ITypeDescriptorContext)this, valueType)) + value = converter.ConvertFrom ((ITypeDescriptorContext)this, + CultureInfo.CurrentCulture, value); + } catch (Exception e) { + error = e.Message; + conversionError = true; + } + if (conversionError) { + string valueText = ConvertToString (value); + string errorShortDescription = null; + if (valueText != null) { + errorShortDescription = "Property value '" + valueText + "' of '" + + PropertyDescriptor.Name + "' is not convertible to type '" + + this.PropertyDescriptor.PropertyType.Name + "'"; + + } else { + errorShortDescription = "Property value of '" + + PropertyDescriptor.Name + "' is not convertible to type '" + + this.PropertyDescriptor.PropertyType.Name + "'"; + } + error = errorShortDescription + Environment.NewLine + Environment.NewLine + error; + return false; + } + } + bool changed = false; bool current_changed = false; object[] propertyOwners = this.PropertyOwners; @@ -516,7 +549,7 @@ protected virtual bool SetValueCore (object value, out string error) if (IsValueType (this.ParentEntry)) current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error); else - current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value); + current_changed = true; } } if (current_changed) diff --git a/mcs/class/corlib/System/TimeZoneInfo.Android.cs b/mcs/class/corlib/System/TimeZoneInfo.Android.cs index fa9f73a4dc68..792a9fe2bc33 100644 --- a/mcs/class/corlib/System/TimeZoneInfo.Android.cs +++ b/mcs/class/corlib/System/TimeZoneInfo.Android.cs @@ -58,10 +58,16 @@ unsafe struct AndroidTzDataEntry { * https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/ZoneInfoDB.java * * This is needed in order to read Android v4.3 tzdata files. + * + * Android 10+ moved the up-to-date tzdata location to a module updatable via the Google Play Store and the + * database location changed (https://source.android.com/devices/architecture/modular-system/runtime#time-zone-data-interactions) + * The older locations still exist (at least the `/system/usr/share/zoneinfo` one) but they won't be updated. */ sealed class AndroidTzData : IAndroidTimeZoneDB { internal static readonly string[] Paths = new string[]{ + GetApexTimeDataRoot () + "/etc/tz/tzdata", // Android 10+, TimeData module where the updates land + GetApexRuntimeRoot () + "/etc/tz/tzdata", // Android 10+, Fallback location if the above isn't found or corrupted Environment.GetEnvironmentVariable ("ANDROID_DATA") + "/misc/zoneinfo/tzdata", Environment.GetEnvironmentVariable ("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata", }; @@ -98,6 +104,26 @@ public string ZoneTab { get {return zoneTab;} } + static string GetApexTimeDataRoot () + { + string ret = Environment.GetEnvironmentVariable ("ANDROID_TZDATA_ROOT"); + if (!String.IsNullOrEmpty (ret)) { + return ret; + } + + return "/apex/com.android.tzdata"; + } + + static string GetApexRuntimeRoot () + { + string ret = Environment.GetEnvironmentVariable ("ANDROID_RUNTIME_ROOT"); + if (!String.IsNullOrEmpty (ret)) { + return ret; + } + + return "/apex/com.android.runtime"; + } + bool LoadData (string path) { if (!File.Exists (path)) diff --git a/mcs/class/corlib/Test/System.Reflection.Emit/EnumBuilderTest.cs b/mcs/class/corlib/Test/System.Reflection.Emit/EnumBuilderTest.cs index 5432040c5235..6a322c6c5e98 100644 --- a/mcs/class/corlib/Test/System.Reflection.Emit/EnumBuilderTest.cs +++ b/mcs/class/corlib/Test/System.Reflection.Emit/EnumBuilderTest.cs @@ -316,6 +316,28 @@ public void TestCreateTypeIncompleteEnumStaticField () Assert.Fail ("Expected CreateInstance of a broken type to throw TLE"); } + [Test] + public void TestCreateInvalidEnumWithAnIncompleteUnderlyingEnumType () + { + var mb = GenerateModule (); + var incomplete = GenerateEnum (mb); + GenerateField (incomplete); + + var eb = mb.DefineEnum ( + _enumNamespace + ".IncompleteUnderlying", + TypeAttributes.Public, + incomplete); + + bool caught = false; + try { + var t = eb.CreateType (); + } catch (TypeLoadException exn) { + caught = true; + } + if (!caught) + Assert.Fail ("Expected CreateType of a broken type to throw TLE"); + } + [Test] public void TestEnumBuilderTokenUsable () { // Regression test for https://bugzilla.xamarin.com/show_bug.cgi?id=58361 diff --git a/mcs/tools/resgen/monoresgen.cs b/mcs/tools/resgen/monoresgen.cs index c5f5ac1e900d..6681bd4e5585 100644 --- a/mcs/tools/resgen/monoresgen.cs +++ b/mcs/tools/resgen/monoresgen.cs @@ -72,7 +72,9 @@ static IResourceReader GetReader (Stream stream, string name, bool useSourcePath return new ResourceReader (stream); case ".resx": var reader = new ResXResourceReader (stream); - reader.BasePath = Path.GetDirectoryName (name); + if (useSourcePath) { + reader.BasePath = Path.GetDirectoryName (name); + } return reader; default: throw new Exception ("Unknown format in file " + name); diff --git a/mono/CMakeLists.txt b/mono/CMakeLists.txt index ab9dc695aaad..6f86d9a26ccf 100644 --- a/mono/CMakeLists.txt +++ b/mono/CMakeLists.txt @@ -1,8 +1,8 @@ -project (mono) +project(mono) -#set (subdirs eglib arch utils cil sgen metadata mini dis profiler) -set (subdirs mini) +#set(subdirs eglib arch utils cil sgen metadata mini dis profiler) +set(subdirs mini profiler) -foreach (dir ${subdirs}) - add_subdirectory (${dir}) -endforeach () +foreach(dir ${subdirs}) + add_subdirectory(${dir}) +endforeach() diff --git a/mono/cil/cil-opcodes.xml b/mono/cil/cil-opcodes.xml index 365b258eaf3d..50b70144d16a 100644 --- a/mono/cil/cil-opcodes.xml +++ b/mono/cil/cil-opcodes.xml @@ -325,4 +325,6 @@ + + diff --git a/mono/cil/opcode.def b/mono/cil/opcode.def index 43722f6d3282..a39b315bac5e 100644 --- a/mono/cil/opcode.def +++ b/mono/cil/opcode.def @@ -325,6 +325,8 @@ OPDEF(CEE_MONO_LDPTR_PROFILER_ALLOCATION_COUNT, "mono_ldptr_profiler_allocation_ OPDEF(CEE_MONO_LD_DELEGATE_METHOD_PTR, "mono_ld_delegate_method_ptr", Pop1, PushI, InlineNone, 0, 2, 0xF0, 0x1E, NEXT) OPDEF(CEE_MONO_RETHROW, "mono_rethrow", PopRef, Push0, InlineNone, 0, 2, 0xF0, 0x1F, ERROR) OPDEF(CEE_MONO_GET_SP, "mono_get_sp", Pop0, PushI, InlineNone, 0, 2, 0xF0, 0x20, NEXT) +OPDEF(CEE_MONO_METHODCONST, "mono_methodconst", Pop0, PushI, InlineI, 0, 2, 0xF0, 0x21, NEXT) +OPDEF(CEE_MONO_PINVOKE_ADDR_CACHE, "mono_pinvoke_addr_cache", Pop0, PushI, InlineI, 0, 2, 0xF0, 0x22, NEXT) #ifndef OPALIAS #define _MONO_CIL_OPALIAS_DEFINED_ #define OPALIAS(a,s,r) diff --git a/mono/eglib/eglib-remap.h b/mono/eglib/eglib-remap.h index c9751049c3b7..6434803195e8 100644 --- a/mono/eglib/eglib-remap.h +++ b/mono/eglib/eglib-remap.h @@ -159,6 +159,7 @@ #define g_ptr_array_sized_new monoeg_g_ptr_array_sized_new #define g_ptr_array_sort monoeg_g_ptr_array_sort #define g_ptr_array_sort_with_data monoeg_g_ptr_array_sort_with_data +#define g_ptr_array_find monoeg_g_ptr_array_find #define g_qsort_with_data monoeg_g_qsort_with_data #define g_queue_free monoeg_g_queue_free #define g_queue_is_empty monoeg_g_queue_is_empty diff --git a/mono/eglib/glib.h b/mono/eglib/glib.h index 4272679aeb13..698ad614e4ee 100644 --- a/mono/eglib/glib.h +++ b/mono/eglib/glib.h @@ -726,6 +726,7 @@ void g_ptr_array_set_size (GPtrArray *array, gint length); gpointer *g_ptr_array_free (GPtrArray *array, gboolean free_seg); void g_ptr_array_foreach (GPtrArray *array, GFunc func, gpointer user_data); guint g_ptr_array_capacity (GPtrArray *array); +gboolean g_ptr_array_find (GPtrArray *array, gconstpointer needle, guint *index); #define g_ptr_array_index(array,index) (array)->pdata[(index)] //FIXME previous missing parens diff --git a/mono/eglib/gptrarray.c b/mono/eglib/gptrarray.c index 09ea774d21c7..18509bc4accf 100644 --- a/mono/eglib/gptrarray.c +++ b/mono/eglib/gptrarray.c @@ -229,3 +229,18 @@ g_ptr_array_capacity (GPtrArray *array) { return ((GPtrArrayPriv *)array)->size; } + +gboolean +g_ptr_array_find (GPtrArray *array, gconstpointer needle, guint *index) +{ + g_assert (array); + for (int i = 0; i < array->len; i++) { + if (array->pdata [i] == needle) { + if (index) + *index = i; + return TRUE; + } + } + + return FALSE; +} diff --git a/mono/metadata/appdomain.c b/mono/metadata/appdomain.c index 11d04619eb13..88686b608281 100644 --- a/mono/metadata/appdomain.c +++ b/mono/metadata/appdomain.c @@ -2480,7 +2480,7 @@ mono_domain_assembly_preload (MonoAssemblyLoadContext *alc, char *base_dir = get_app_context_base_directory (error); search_path [0] = base_dir; - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Domain %s (%p) ApplicationBase is %s", domain->friendly_name, domain, base_dir); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Domain (%p) ApplicationBase is %s", domain, base_dir); result = real_load (search_path, aname->culture, aname->name, &req); @@ -2821,7 +2821,7 @@ mono_alc_load_raw_bytes (MonoAssemblyLoadContext *alc, guint8 *assembly_data, gu { MonoAssembly *ass = NULL; MonoImageOpenStatus status; - MonoImage *image = mono_image_open_from_data_internal (alc, (char*)assembly_data, raw_assembly_len, FALSE, NULL, refonly, FALSE, NULL); + MonoImage *image = mono_image_open_from_data_internal (alc, (char*)assembly_data, raw_assembly_len, FALSE, NULL, refonly, FALSE, NULL, NULL); if (!image) { mono_error_set_bad_image_by_name (error, "In memory assembly", "0x%p", assembly_data); diff --git a/mono/metadata/appdomain.h b/mono/metadata/appdomain.h index ab260646e900..bc8a1f2de199 100644 --- a/mono/metadata/appdomain.h +++ b/mono/metadata/appdomain.h @@ -106,6 +106,9 @@ mono_domain_foreach (MonoDomainFunc func, void* user_data); MONO_API MONO_RT_EXTERNAL_ONLY MonoAssembly * mono_domain_assembly_open (MonoDomain *domain, const char *name); +MONO_API void +mono_domain_ensure_entry_assembly (MonoDomain *domain, MonoAssembly *assembly); + MONO_API mono_bool mono_domain_finalize (MonoDomain *domain, uint32_t timeout); diff --git a/mono/metadata/assembly-load-context.c b/mono/metadata/assembly-load-context.c index 7aa927d21af2..ec46fbc2b528 100644 --- a/mono/metadata/assembly-load-context.c +++ b/mono/metadata/assembly-load-context.c @@ -15,7 +15,7 @@ GENERATE_GET_CLASS_WITH_CACHE (assembly_load_context, "System.Runtime.Loader", "AssemblyLoadContext"); -void +static void mono_alc_init (MonoAssemblyLoadContext *alc, MonoDomain *domain, gboolean collectible) { MonoLoadedImages *li = g_new0 (MonoLoadedImages, 1); @@ -30,36 +30,63 @@ mono_alc_init (MonoAssemblyLoadContext *alc, MonoDomain *domain, gboolean collec mono_coop_mutex_init (&alc->pinvoke_lock); } +static MonoAssemblyLoadContext * +mono_alc_create (MonoDomain *domain, gboolean is_default, gboolean collectible) +{ + MonoAssemblyLoadContext *alc = NULL; + + mono_domain_alcs_lock (domain); + if (is_default && domain->default_alc) + goto leave; + + alc = g_new0 (MonoAssemblyLoadContext, 1); + mono_alc_init (alc, domain, collectible); + + domain->alcs = g_slist_prepend (domain->alcs, alc); + if (is_default) + domain->default_alc = alc; + +leave: + mono_domain_alcs_unlock (domain); + return alc; +} + void -mono_alc_cleanup (MonoAssemblyLoadContext *alc) +mono_alc_create_default (MonoDomain *domain) { - /* - * This is still very much WIP. It needs to be split up into various other functions and adjusted to work with the - * managed LoaderAllocator design. For now, I've put it all in this function, but don't look at it too closely. - * - * Of particular note: the minimum refcount on assemblies is 2: one for the domain and one for the ALC. - * The domain refcount might be less than optimal on netcore, but its removal is too likely to cause issues for now. - */ - GSList *tmp; - MonoDomain *domain = alc->domain; + if (domain->default_alc) + return; + mono_alc_create (domain, TRUE, FALSE); +} - g_assert (alc != mono_domain_default_alc (domain)); - g_assert (alc->collectible == TRUE); +MonoAssemblyLoadContext * +mono_alc_create_individual (MonoDomain *domain, MonoGCHandle this_gchandle, gboolean collectible, MonoError *error) +{ + MonoAssemblyLoadContext *alc = mono_alc_create (domain, FALSE, collectible); + + alc->gchandle = this_gchandle; + + return alc; +} - // FIXME: alc unloading profiler event +static void +mono_alc_cleanup_assemblies (MonoAssemblyLoadContext *alc) +{ + // The minimum refcount on assemblies is 2: one for the domain and one for the ALC. + // The domain refcount might be less than optimal on netcore, but its removal is too likely to cause issues for now. + GSList *tmp; + MonoDomain *domain = alc->domain; // Remove the assemblies from domain_assemblies mono_domain_assemblies_lock (domain); for (tmp = alc->loaded_assemblies; tmp; tmp = tmp->next) { MonoAssembly *assembly = (MonoAssembly *)tmp->data; - g_slist_remove (domain->domain_assemblies, assembly); - mono_atomic_dec_i32 (&assembly->ref_count); + domain->domain_assemblies = g_slist_remove (domain->domain_assemblies, assembly); + mono_assembly_decref (assembly); mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Unloading ALC [%p], removing assembly %s[%p] from domain_assemblies, ref_count=%d\n", alc, assembly->aname.name, assembly, assembly->ref_count); } mono_domain_assemblies_unlock (domain); - // Some equivalent to mono_gc_clear_domain? I guess in our case we just have to assert that we have no lingering references? - // Release the GC roots for (tmp = alc->loaded_assemblies; tmp; tmp = tmp->next) { MonoAssembly *assembly = (MonoAssembly *)tmp->data; @@ -99,17 +126,48 @@ mono_alc_cleanup (MonoAssemblyLoadContext *alc) g_slist_free (alc->loaded_assemblies); alc->loaded_assemblies = NULL; - // FIXME: alc unloaded profiler event - - g_hash_table_destroy (alc->pinvoke_scopes); mono_coop_mutex_destroy (&alc->assemblies_lock); - mono_coop_mutex_destroy (&alc->pinvoke_lock); mono_loaded_images_free (alc->loaded_images); + alc->loaded_images = NULL; // TODO: free mempool stuff/jit info tables, see domain freeing for an example } +static void +mono_alc_cleanup (MonoAssemblyLoadContext *alc) +{ + MonoDomain *domain = alc->domain; + + g_assert (alc != mono_domain_default_alc (domain)); + g_assert (alc->collectible == TRUE); + + // TODO: alc unloading profiler event + + // Remove from domain list + mono_domain_alcs_lock (domain); + domain->alcs = g_slist_remove (domain->alcs, alc); + mono_domain_alcs_unlock (domain); + + mono_alc_cleanup_assemblies (alc); + + mono_gchandle_free_internal (alc->gchandle); + alc->gchandle = NULL; + + g_hash_table_destroy (alc->pinvoke_scopes); + alc->pinvoke_scopes = NULL; + mono_coop_mutex_destroy (&alc->pinvoke_lock); + + // TODO: alc unloaded profiler event +} + +static void +mono_alc_free (MonoAssemblyLoadContext *alc) +{ + mono_alc_cleanup (alc); + g_free (alc); +} + void mono_alc_assemblies_lock (MonoAssemblyLoadContext *alc) { @@ -136,10 +194,9 @@ ves_icall_System_Runtime_Loader_AssemblyLoadContext_InternalInitializeNativeALC g_assert (alc); if (!alc->gchandle) alc->gchandle = this_gchandle; - } else { - /* create it */ - alc = mono_domain_create_individual_alc (domain, this_gchandle, collectible, error); - } + } else + alc = mono_alc_create_individual (domain, this_gchandle, collectible, error); + return alc; } @@ -149,10 +206,13 @@ ves_icall_System_Runtime_Loader_AssemblyLoadContext_PrepareForAssemblyLoadContex MonoGCHandle strong_gchandle = (MonoGCHandle)strong_gchandle_ptr; MonoAssemblyLoadContext *alc = (MonoAssemblyLoadContext *)alc_pointer; - g_assert (alc->collectible == TRUE); - g_assert (alc->unloading == FALSE); + g_assert (alc->collectible); + g_assert (!alc->unloading); + g_assert (alc->gchandle); + alc->unloading = TRUE; + // Replace the weak gchandle with the new strong one to keep the managed ALC alive MonoGCHandle weak_gchandle = alc->gchandle; alc->gchandle = strong_gchandle; mono_gchandle_free_internal (weak_gchandle); @@ -176,9 +236,10 @@ mono_alc_is_default (MonoAssemblyLoadContext *alc) MonoAssemblyLoadContext * mono_alc_from_gchandle (MonoGCHandle alc_gchandle) { + HANDLE_FUNCTION_ENTER (); MonoManagedAssemblyLoadContextHandle managed_alc = MONO_HANDLE_CAST (MonoManagedAssemblyLoadContext, mono_gchandle_get_target_handle (alc_gchandle)); - MonoAssemblyLoadContext *alc = (MonoAssemblyLoadContext *)MONO_HANDLE_GETVAL (managed_alc, native_assembly_load_context); - return alc; + MonoAssemblyLoadContext *alc = MONO_HANDLE_GETVAL (managed_alc, native_assembly_load_context); + HANDLE_FUNCTION_RETURN_VAL (alc); } MonoGCHandle diff --git a/mono/metadata/assembly.c b/mono/metadata/assembly.c index 5f37bb0c8e23..4d8893bdc8d6 100644 --- a/mono/metadata/assembly.c +++ b/mono/metadata/assembly.c @@ -1310,10 +1310,16 @@ assemblyref_public_tok_checked (MonoImage *image, guint32 key_index, guint32 fla * The reference count is reduced every time the method mono_assembly_close() is * invoked. */ -void +gint32 mono_assembly_addref (MonoAssembly *assembly) { - mono_atomic_inc_i32 (&assembly->ref_count); + return mono_atomic_inc_i32 (&assembly->ref_count); +} + +gint32 +mono_assembly_decref (MonoAssembly *assembly) +{ + return mono_atomic_dec_i32 (&assembly->ref_count); } /* @@ -1644,6 +1650,27 @@ load_reference_by_aname_individual_asmctx (MonoAssemblyName *aname, MonoAssembly return reference; } #else +static MonoAssembly * +search_bundle_for_assembly (MonoAssemblyLoadContext *alc, MonoAssemblyName *aname) +{ + if (bundles == NULL) + return NULL; + + MonoImageOpenStatus status; + MonoImage *image; + MonoAssemblyLoadRequest req; + image = mono_assembly_open_from_bundle (alc, aname->name, &status, FALSE); + if (!image) { + char *name = g_strdup_printf ("%s.dll", aname->name); + image = mono_assembly_open_from_bundle (alc, name, &status, FALSE); + } + if (image) { + mono_assembly_request_prepare_load (&req, MONO_ASMCTX_DEFAULT, alc); + return mono_assembly_request_load_from (image, aname->name, &req, &status); + } + return NULL; +} + static MonoAssembly* netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, MonoAssembly *requesting, gboolean postload) { @@ -1665,61 +1692,85 @@ netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, M * Try these until one of them succeeds (by returning a non-NULL reference): * 1. Check if it's already loaded by the ALC. * - * 2. If it's a non-default ALC, call the Load() method. + * 2. If we have a bundle registered, search the images for a matching name. + * + * 3. If it's a non-default ALC, call the Load() method. * - * 3. If the ALC is not the default and this is not a satellite request, + * 4. If the ALC is not the default and this is not a satellite request, * check if it's already loaded by the default ALC. * - * 4. If the ALC is the default or this is not a satellite request, + * 5. If the ALC is the default or this is not a satellite request, * check the TPA list, APP_PATHS, and ApplicationBase. * - * 5. If this is a satellite request, call the ALC ResolveSatelliteAssembly method. + * 6. If this is a satellite request, call the ALC ResolveSatelliteAssembly method. * - * 6. Call the ALC Resolving event. + * 7. Call the ALC Resolving event. * - * 7. Call the ALC AssemblyResolve event (except for corlib satellite assemblies). + * 8. Call the ALC AssemblyResolve event (except for corlib satellite assemblies). * - * 8. Return NULL. + * 9. Return NULL. */ reference = mono_assembly_loaded_internal (alc, aname, FALSE); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly already loaded in the active ALC: '%s'.", aname->name); goto leave; + } + + if (bundles != NULL) { + reference = search_bundle_for_assembly (alc, aname); + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found in the bundle: '%s'.", aname->name); + goto leave; + } + } if (!is_default) { reference = mono_alc_invoke_resolve_using_load_nofail (alc, aname); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found using Load method: '%s'.", aname->name); goto leave; + } } if (!is_default && !is_satellite) { reference = mono_assembly_loaded_internal (mono_domain_default_alc (mono_alc_domain (alc)), aname, FALSE); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly already loaded in the default ALC: '%s'.", aname->name); goto leave; + } } if (is_default || !is_satellite) { reference = invoke_assembly_preload_hook (mono_domain_default_alc (mono_alc_domain (alc)), aname, assemblies_path); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with the filesystem probing logic: '%s'.", aname->name); goto leave; + } } if (is_satellite) { reference = mono_alc_invoke_resolve_using_resolve_satellite_nofail (alc, aname); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with ResolveSatelliteAssembly method: '%s'.", aname->name); goto leave; + } } reference = mono_alc_invoke_resolve_using_resolving_event_nofail (alc, aname); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with the Resolving event: '%s'.", aname->name); goto leave; + } // Looking up corlib resources here can cause an infinite loop // See: https://github.com/dotnet/coreclr/blob/0a762eb2f3a299489c459da1ddeb69e042008f07/src/vm/appdomain.cpp#L5178-L5239 if (!(strcmp (aname->name, MONO_ASSEMBLY_CORLIB_RESOURCE_NAME) == 0 && is_satellite) && postload) { reference = mono_assembly_invoke_search_hook_internal (alc, requesting, aname, FALSE, TRUE); - if (reference) + if (reference) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found with AssemblyResolve event: '%s'.", aname->name); goto leave; + } } leave: @@ -2502,7 +2553,12 @@ mono_assembly_open_from_bundle (MonoAssemblyLoadContext *alc, const char *filena name = g_path_get_basename (filename); for (i = 0; !image && bundles [i]; ++i) { if (strcmp (bundles [i]->name, is_satellite ? filename : name) == 0) { - image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name); +#ifdef ENABLE_NETCORE + // Since bundled images don't exist on disk, don't give them a legit filename + image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, NULL); +#else + image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, name); +#endif break; } } @@ -2725,8 +2781,10 @@ mono_assembly_request_open (const char *filename, const MonoAssemblyOpenRequest if (!loaded_from_bundle) mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly Loader loaded assembly from location: '%s'.", filename); +#ifndef ENABLE_NETCORE // no XML file loading on netcore if (!refonly) mono_config_for_assembly_internal (ass->image); +#endif } /* Clear the reference added by mono_image_open */ @@ -4851,20 +4909,7 @@ mono_assembly_request_byname (MonoAssemblyName *aname, const MonoAssemblyByNameR result = prevent_reference_assembly_from_running (result, refonly); } #else - result = NULL; - if (bundles != NULL) { - MonoImageOpenStatus status; - MonoImage *image; - image = mono_assembly_open_from_bundle (req->request.alc, aname->name, &status, FALSE); - if (!image) { - char *name = g_strdup_printf ("%s.dll", aname->name); - image = mono_assembly_open_from_bundle (req->request.alc, name, &status, FALSE); - } - if (image) - result = mono_assembly_request_load_from (image, aname->name, &req->request, &status); - } - if (!result) - result = netcore_load_reference (aname, req->request.alc, req->requesting_assembly, !req->no_postload_search); + result = netcore_load_reference (aname, req->request.alc, req->requesting_assembly, !req->no_postload_search); #endif return result; } @@ -5020,7 +5065,7 @@ mono_assembly_close_except_image_pools (MonoAssembly *assembly) return FALSE; /* Might be 0 already */ - if (mono_atomic_dec_i32 (&assembly->ref_count) > 0) + if (mono_assembly_decref (assembly) > 0) return FALSE; MONO_PROFILER_RAISE (assembly_unloading, (assembly)); diff --git a/mono/metadata/debug-mono-ppdb.c b/mono/metadata/debug-mono-ppdb.c index c4dd3d1d4d67..38c619a61b85 100644 --- a/mono/metadata/debug-mono-ppdb.c +++ b/mono/metadata/debug-mono-ppdb.c @@ -194,7 +194,7 @@ mono_ppdb_load_file (MonoImage *image, const guint8 *raw_contents, int size) MonoAssemblyLoadContext *alc = mono_image_get_alc (image); if (raw_contents) { if (size > 4 && strncmp ((char*)raw_contents, "BSJB", 4) == 0) - ppdb_image = mono_image_open_from_data_internal (alc, (char*)raw_contents, size, TRUE, &status, FALSE, TRUE, NULL); + ppdb_image = mono_image_open_from_data_internal (alc, (char*)raw_contents, size, TRUE, &status, FALSE, TRUE, NULL, NULL); } else { /* ppdb files drop the .exe/.dll extension */ filename = mono_image_get_filename (image); diff --git a/mono/metadata/domain-internals.h b/mono/metadata/domain-internals.h index 2acdd85d20d4..3968a773ddf4 100644 --- a/mono/metadata/domain-internals.h +++ b/mono/metadata/domain-internals.h @@ -692,15 +692,21 @@ mono_runtime_install_appctx_properties (void); gboolean mono_domain_set_fast (MonoDomain *domain, gboolean force); -void -mono_domain_ensure_entry_assembly (MonoDomain *domain, MonoAssembly *assembly); - MonoAssemblyLoadContext * mono_domain_default_alc (MonoDomain *domain); #ifdef ENABLE_NETCORE -MonoAssemblyLoadContext * -mono_domain_create_individual_alc (MonoDomain *domain, MonoGCHandle this_gchandle, gboolean collectible, MonoError *error); +static inline void +mono_domain_alcs_lock (MonoDomain *domain) +{ + mono_coop_mutex_lock (&domain->alcs_lock); +} + +static inline void +mono_domain_alcs_unlock (MonoDomain *domain) +{ + mono_coop_mutex_unlock (&domain->alcs_lock); +} #endif static inline diff --git a/mono/metadata/domain.c b/mono/metadata/domain.c index 4f4294adda2e..36271f8c2022 100644 --- a/mono/metadata/domain.c +++ b/mono/metadata/domain.c @@ -128,17 +128,6 @@ get_runtimes_from_exe (const char *exe_file, MonoImage **exe_image); static const MonoRuntimeInfo* get_runtime_by_version (const char *version); -#ifdef ENABLE_NETCORE -static void -mono_domain_alcs_lock (MonoDomain *domain); - -static void -mono_domain_alcs_unlock (MonoDomain *domain); - -static void -mono_domain_create_default_alc (MonoDomain *domain); -#endif - static LockFreeMempool* lock_free_mempool_new (void) { @@ -482,7 +471,7 @@ mono_domain_create (void) mono_debug_domain_create (domain); #ifdef ENABLE_NETCORE - mono_domain_create_default_alc (domain); + mono_alc_create_default (domain); #endif if (create_domain_hook) @@ -2075,60 +2064,3 @@ mono_domain_default_alc (MonoDomain *domain) return domain->default_alc; #endif } - -#ifdef ENABLE_NETCORE -static inline void -mono_domain_alcs_lock (MonoDomain *domain) -{ - mono_coop_mutex_lock (&domain->alcs_lock); -} - -static inline void -mono_domain_alcs_unlock (MonoDomain *domain) -{ - mono_coop_mutex_unlock (&domain->alcs_lock); -} - -static MonoAssemblyLoadContext * -create_alc (MonoDomain *domain, gboolean is_default, gboolean collectible) -{ - MonoAssemblyLoadContext *alc = NULL; - - mono_domain_alcs_lock (domain); - if (is_default && domain->default_alc) - goto leave; - - alc = g_new0 (MonoAssemblyLoadContext, 1); - mono_alc_init (alc, domain, collectible); - - domain->alcs = g_slist_prepend (domain->alcs, alc); - if (is_default) - domain->default_alc = alc; -leave: - mono_domain_alcs_unlock (domain); - return alc; -} - -void -mono_domain_create_default_alc (MonoDomain *domain) -{ - if (domain->default_alc) - return; - create_alc (domain, TRUE, FALSE); -} - -MonoAssemblyLoadContext * -mono_domain_create_individual_alc (MonoDomain *domain, MonoGCHandle this_gchandle, gboolean collectible, MonoError *error) -{ - MonoAssemblyLoadContext *alc = create_alc (domain, FALSE, collectible); - alc->gchandle = this_gchandle; - return alc; -} - -static void -mono_alc_free (MonoAssemblyLoadContext *alc) -{ - mono_alc_cleanup (alc); - g_free (alc); -} -#endif diff --git a/mono/metadata/filewatcher.c b/mono/metadata/filewatcher.c index 69ab3a89fb08..cf1646114cd2 100644 --- a/mono/metadata/filewatcher.c +++ b/mono/metadata/filewatcher.c @@ -10,6 +10,7 @@ */ #include +#include #if !ENABLE_NETCORE @@ -186,4 +187,8 @@ ves_icall_System_IO_KqueueMonitor_kevent_notimeout (int *kq_ptr, gpointer change #endif /* #if HAVE_KQUEUE */ +#else + +MONO_EMPTY_SOURCE_FILE (filewatcher); + #endif /* !ENABLE_NETCORE */ diff --git a/mono/metadata/handle.h b/mono/metadata/handle.h index 6a9ad056deb7..0be5c18d206f 100644 --- a/mono/metadata/handle.h +++ b/mono/metadata/handle.h @@ -517,7 +517,10 @@ TYPED_HANDLE_DECL (MonoException); TYPED_HANDLE_DECL (MonoAppContext); /* Simpler version of MONO_HANDLE_NEW if the handle is not used */ -#define MONO_HANDLE_PIN(object) MONO_HANDLE_NEW (MonoObject, (object)) +#define MONO_HANDLE_PIN(object) do { \ + if ((object) != NULL) \ + MONO_HANDLE_NEW (MonoObject, (MonoObject*)(object)); \ + } while (0) // Structs cannot be cast to structs. // As well, a function is needed because an anonymous struct cannot be initialized in C. diff --git a/mono/metadata/icall-decl.h b/mono/metadata/icall-decl.h index 2c153f8cd6c9..858a7b8830a6 100644 --- a/mono/metadata/icall-decl.h +++ b/mono/metadata/icall-decl.h @@ -304,4 +304,8 @@ ICALL_EXPORT gint32 ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWait ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal (gpointer sem_ptr, gint32 count); #endif +#if defined(ENABLE_NETCORE) && defined(TARGET_AMD64) +ICALL_EXPORT void ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int abcd[4], int function_id, int subfunction_id); +#endif + #endif // __MONO_METADATA_ICALL_DECL_H__ diff --git a/mono/metadata/icall-def-netcore.h b/mono/metadata/icall-def-netcore.h index 8e3a61820b65..60283d09e9da 100644 --- a/mono/metadata/icall-def-netcore.h +++ b/mono/metadata/icall-def-netcore.h @@ -373,6 +373,11 @@ HANDLES(NATIVEL_2, "GetSymbol", ves_icall_System_Runtime_InteropServices_NativeL HANDLES(NATIVEL_3, "LoadByName", ves_icall_System_Runtime_InteropServices_NativeLibrary_LoadByName, gpointer, 5, (MonoString, MonoReflectionAssembly, MonoBoolean, guint32, MonoBoolean)) HANDLES(NATIVEL_4, "LoadFromPath", ves_icall_System_Runtime_InteropServices_NativeLibrary_LoadFromPath, gpointer, 2, (MonoString, MonoBoolean)) +#if defined(TARGET_AMD64) +ICALL_TYPE(X86BASE, "System.Runtime.Intrinsics.X86.X86Base", X86BASE_1) +NOHANDLES(ICALL(X86BASE_1, "__cpuidex", ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex)) +#endif + ICALL_TYPE(ALC, "System.Runtime.Loader.AssemblyLoadContext", ALC_5) HANDLES(ALC_5, "GetLoadContextForAssembly", ves_icall_System_Runtime_Loader_AssemblyLoadContext_GetLoadContextForAssembly, gpointer, 1, (MonoReflectionAssembly)) HANDLES(ALC_4, "InternalGetLoadedAssemblies", ves_icall_System_Runtime_Loader_AssemblyLoadContext_InternalGetLoadedAssemblies, MonoArray, 0, ()) diff --git a/mono/metadata/icall-eventpipe.c b/mono/metadata/icall-eventpipe.c index 4222ffb72e80..ae86f89ac426 100644 --- a/mono/metadata/icall-eventpipe.c +++ b/mono/metadata/icall-eventpipe.c @@ -320,7 +320,7 @@ ves_icall_System_Diagnostics_Tracing_EventPipeInternal_Enable ( (EventPipeSerializationFormat)format, true, NULL, - true); + NULL); ep_start_streaming (session_id); if (config_providers) { @@ -341,7 +341,7 @@ ves_icall_System_Diagnostics_Tracing_EventPipeInternal_EventActivityIdControl ( /* GUID * */uint8_t *activity_id) { int32_t result = 0; - EventPipeThread *thread = ep_thread_get (); + EventPipeThread *thread = ep_thread_get_or_create (); if (thread == NULL) return 1; @@ -359,9 +359,14 @@ ves_icall_System_Diagnostics_Tracing_EventPipeInternal_EventActivityIdControl ( ep_thread_create_activity_id (activity_id, EP_ACTIVITY_ID_SIZE); break; case EP_ACTIVITY_CONTROL_GET_SET_ID: + ep_thread_get_activity_id (thread, current_activity_id, EP_ACTIVITY_ID_SIZE); + ep_thread_set_activity_id (thread, activity_id, EP_ACTIVITY_ID_SIZE); + memcpy (activity_id, current_activity_id, EP_ACTIVITY_ID_SIZE); + break; + case EP_ACTIVITY_CONTROL_CREATE_SET_ID: ep_thread_get_activity_id (thread, activity_id, EP_ACTIVITY_ID_SIZE); - ep_thread_create_activity_id (current_activity_id, G_N_ELEMENTS (current_activity_id)); - ep_thread_set_activity_id (thread, current_activity_id, G_N_ELEMENTS (current_activity_id)); + ep_thread_create_activity_id (current_activity_id, EP_ACTIVITY_ID_SIZE); + ep_thread_set_activity_id (thread, current_activity_id, EP_ACTIVITY_ID_SIZE); break; default: result = 1; diff --git a/mono/metadata/icall.c b/mono/metadata/icall.c index 948b43f9dec5..df5d20a7f32f 100644 --- a/mono/metadata/icall.c +++ b/mono/metadata/icall.c @@ -7230,7 +7230,7 @@ ves_icall_System_Reflection_RuntimeModule_ResolveSignature (MonoImage *image, gu } static void -check_for_invalid_type (MonoClass *klass, MonoError *error) +check_for_invalid_array_type (MonoClass *klass, MonoError *error) { char *name; @@ -7243,13 +7243,23 @@ check_for_invalid_type (MonoClass *klass, MonoError *error) mono_error_set_type_load_name (error, name, g_strdup (""), ""); } +static void +check_for_invalid_byref_or_pointer_type (MonoClass *klass, MonoError *error) +{ +#ifdef ENABLE_NETCORE + return; +#else + check_for_invalid_array_type (klass, error); +#endif +} + MonoReflectionTypeHandle ves_icall_RuntimeType_make_array_type (MonoReflectionTypeHandle ref_type, int rank, MonoError *error) { MonoType *type = MONO_HANDLE_GETVAL (ref_type, type); MonoClass *klass = mono_class_from_mono_type_internal (type); - check_for_invalid_type (klass, error); + check_for_invalid_array_type (klass, error); return_val_if_nok (error, MONO_HANDLE_CAST (MonoReflectionType, NULL_HANDLE)); MonoClass *aklass; @@ -7276,7 +7286,7 @@ ves_icall_RuntimeType_make_byref_type (MonoReflectionTypeHandle ref_type, MonoEr mono_class_init_checked (klass, error); return_val_if_nok (error, MONO_HANDLE_CAST (MonoReflectionType, NULL_HANDLE)); - check_for_invalid_type (klass, error); + check_for_invalid_byref_or_pointer_type (klass, error); return_val_if_nok (error, MONO_HANDLE_CAST (MonoReflectionType, NULL_HANDLE)); MonoDomain *domain = MONO_HANDLE_DOMAIN (ref_type); @@ -7292,7 +7302,7 @@ ves_icall_RuntimeType_MakePointerType (MonoReflectionTypeHandle ref_type, MonoEr mono_class_init_checked (klass, error); return_val_if_nok (error, MONO_HANDLE_CAST (MonoReflectionType, NULL_HANDLE)); - check_for_invalid_type (klass, error); + check_for_invalid_byref_or_pointer_type (klass, error); return_val_if_nok (error, MONO_HANDLE_CAST (MonoReflectionType, NULL_HANDLE)); MonoClass *pklass = mono_class_create_ptr (type); diff --git a/mono/metadata/image.c b/mono/metadata/image.c index d0b6c8b42089..2c1a454702de 100644 --- a/mono/metadata/image.c +++ b/mono/metadata/image.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #ifdef HAVE_UNISTD_H @@ -1882,7 +1883,7 @@ register_image (MonoLoadedImages *li, MonoImage *image, gboolean *problematic) } MonoImage * -mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only, const char *name) +mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only, const char *name, const char *filename) { MonoCLIImageInfo *iinfo; MonoImage *image; @@ -1904,12 +1905,12 @@ mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, gu memcpy (datac, data, data_len); } - MonoImageStorage *storage = mono_image_storage_new_raw_data (datac, data_len, need_copy, name); + MonoImageStorage *storage = mono_image_storage_new_raw_data (datac, data_len, need_copy, filename); image = g_new0 (MonoImage, 1); image->storage = storage; mono_image_init_raw_data (image, storage); - image->name = (name == NULL) ? g_strdup_printf ("data-%p", datac) : g_strdup(name); - image->filename = name ? g_strdup (name) : NULL; + image->name = (name == NULL) ? g_strdup_printf ("data-%p", datac) : g_strdup (name); + image->filename = filename ? g_strdup (filename) : NULL; iinfo = g_new0 (MonoCLIImageInfo, 1); image->image_info = iinfo; image->ref_only = refonly; @@ -1926,6 +1927,21 @@ mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, gu return register_image (mono_alc_get_loaded_images (alc), image, NULL); } +MonoImage * +mono_image_open_from_data_alc (MonoAssemblyLoadContextGCHandle alc_gchandle, char *data, uint32_t data_len, mono_bool need_copy, MonoImageOpenStatus *status, const char *name) +{ + MonoImage *result; + MONO_ENTER_GC_UNSAFE; +#ifdef ENABLE_NETCORE + MonoAssemblyLoadContext *alc = mono_alc_from_gchandle (alc_gchandle); +#else + MonoAssemblyLoadContext *alc = mono_domain_default_alc (mono_domain_get ()); +#endif + result = mono_image_open_from_data_internal (alc, data, data_len, need_copy, status, FALSE, FALSE, name, name); + MONO_EXIT_GC_UNSAFE; + return result; +} + /** * mono_image_open_from_data_with_name: */ @@ -1935,7 +1951,7 @@ mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need MonoImage *result; MONO_ENTER_GC_UNSAFE; MonoDomain *domain = mono_domain_get (); - result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, refonly, FALSE, name); + result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, refonly, FALSE, name, name); MONO_EXIT_GC_UNSAFE; return result; } @@ -1949,7 +1965,7 @@ mono_image_open_from_data_full (char *data, guint32 data_len, gboolean need_copy MonoImage *result; MONO_ENTER_GC_UNSAFE; MonoDomain *domain = mono_domain_get (); - result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, refonly, FALSE, NULL); + result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, refonly, FALSE, NULL, NULL); MONO_EXIT_GC_UNSAFE; return result; } @@ -1963,7 +1979,7 @@ mono_image_open_from_data (char *data, guint32 data_len, gboolean need_copy, Mon MonoImage *result; MONO_ENTER_GC_UNSAFE; MonoDomain *domain = mono_domain_get (); - result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, FALSE, FALSE, NULL); + result = mono_image_open_from_data_internal (mono_domain_default_alc (domain), data, data_len, need_copy, status, FALSE, FALSE, NULL, NULL); MONO_EXIT_GC_UNSAFE; return result; } diff --git a/mono/metadata/jit-icall-reg.h b/mono/metadata/jit-icall-reg.h index 3abbdf45da4d..a9bff58883d7 100644 --- a/mono/metadata/jit-icall-reg.h +++ b/mono/metadata/jit-icall-reg.h @@ -340,6 +340,7 @@ MONO_JIT_ICALL (ves_icall_runtime_class_init) \ MONO_JIT_ICALL (ves_icall_string_alloc) \ MONO_JIT_ICALL (ves_icall_string_new_wrapper) \ MONO_JIT_ICALL (ves_icall_thread_finish_async_abort) \ +MONO_JIT_ICALL (mono_marshal_lookup_pinvoke) \ \ MONO_JIT_ICALL (count) \ diff --git a/mono/metadata/loader-internals.h b/mono/metadata/loader-internals.h index 43134540d59a..6d5f493c8140 100644 --- a/mono/metadata/loader-internals.h +++ b/mono/metadata/loader-internals.h @@ -90,10 +90,10 @@ void mono_set_pinvoke_search_directories (int dir_count, char **dirs); void -mono_alc_init (MonoAssemblyLoadContext *alc, MonoDomain *domain, gboolean collectible); +mono_alc_create_default (MonoDomain *domain); -void -mono_alc_cleanup (MonoAssemblyLoadContext *alc); +MonoAssemblyLoadContext * +mono_alc_create_individual (MonoDomain *domain, MonoGCHandle this_gchandle, gboolean collectible, MonoError *error); void mono_alc_assemblies_lock (MonoAssemblyLoadContext *alc); @@ -115,7 +115,6 @@ mono_alc_invoke_resolve_using_resolve_satellite_nofail (MonoAssemblyLoadContext MonoAssemblyLoadContext * mono_alc_from_gchandle (MonoGCHandle alc_gchandle); - #endif /* ENABLE_NETCORE */ static inline MonoDomain * diff --git a/mono/metadata/marshal-ilgen.c b/mono/metadata/marshal-ilgen.c index ecff9d08de86..0d80e942e796 100644 --- a/mono/metadata/marshal-ilgen.c +++ b/mono/metadata/marshal-ilgen.c @@ -2023,6 +2023,7 @@ emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSi MonoClass *klass; int i, argnum, *tmp_locals; int type, param_shift = 0; + int func_addr_local = -1; gboolean need_gc_safe = FALSE; GCSafeTransitionBuilder gc_safe_transition_builder; @@ -2067,9 +2068,43 @@ emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSi mono_mb_add_local (mb, sig->ret); } - if (need_gc_safe) { - gc_safe_transition_builder_add_locals (&gc_safe_transition_builder); + if (need_gc_safe) + gc_safe_transition_builder_add_locals (&gc_safe_transition_builder); + +#ifdef ENABLE_NETCORE + if (!func && !aot && !func_param && !MONO_CLASS_IS_IMPORT (mb->method->klass)) { + /* + * On netcore, its possible to register pinvoke resolvers at runtime, so + * a pinvoke lookup can fail, and then succeed later. So if the + * original lookup failed, do a lookup every time until it + * succeeds. + * This adds some overhead, but only when the pinvoke lookup + * was not initially successful. + * FIXME: AOT case + */ + func_addr_local = mono_mb_add_local (mb, int_type); + + int cache_local = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_PINVOKE_ADDR_CACHE, &piinfo->method); + mono_mb_emit_stloc (mb, cache_local); + + mono_mb_emit_ldloc (mb, cache_local); + mono_mb_emit_byte (mb, CEE_LDIND_I); + int pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + + mono_mb_emit_ldloc (mb, cache_local); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_METHODCONST, &piinfo->method); + mono_mb_emit_icall (mb, mono_marshal_lookup_pinvoke); + mono_mb_emit_byte (mb, CEE_STIND_I); + + mono_mb_patch_branch (mb, pos); + mono_mb_emit_ldloc (mb, cache_local); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, func_addr_local); } +#endif /* * cookie = mono_threads_enter_gc_safe_region_unbalanced (ref dummy); @@ -2139,22 +2174,23 @@ emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSi g_assert_not_reached (); #endif } else { - if (aot) { - /* Reuse the ICALL_ADDR opcode for pinvokes too */ - mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - mono_mb_emit_op (mb, CEE_MONO_ICALL_ADDR, &piinfo->method); - if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) { - mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - mono_mb_emit_byte (mb, CEE_MONO_SAVE_LAST_ERROR); - } - mono_mb_emit_calli (mb, csig); + if (func_addr_local != -1) { + mono_mb_emit_ldloc (mb, func_addr_local); } else { - if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) { + if (aot) { + /* Reuse the ICALL_ADDR opcode for pinvokes too */ mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - mono_mb_emit_byte (mb, CEE_MONO_SAVE_LAST_ERROR); + mono_mb_emit_op (mb, CEE_MONO_ICALL_ADDR, &piinfo->method); } - mono_mb_emit_native_call (mb, csig, func); } + if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) { + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_byte (mb, CEE_MONO_SAVE_LAST_ERROR); + } + if (func_addr_local != -1 || aot) + mono_mb_emit_calli (mb, csig); + else + mono_mb_emit_native_call (mb, csig, func); } if (MONO_TYPE_ISSTRUCT (sig->ret)) { diff --git a/mono/metadata/marshal.c b/mono/metadata/marshal.c index 3faa290b12c0..146f90d2b3b9 100644 --- a/mono/metadata/marshal.c +++ b/mono/metadata/marshal.c @@ -255,6 +255,7 @@ mono_marshal_init (void) register_icall (mono_threads_attach_coop, mono_icall_sig_ptr_ptr_ptr, TRUE); register_icall (mono_threads_detach_coop, mono_icall_sig_void_ptr_ptr, TRUE); register_icall (mono_marshal_get_type_object, mono_icall_sig_object_ptr, TRUE); + register_icall (mono_marshal_lookup_pinvoke, mono_icall_sig_ptr_ptr, FALSE); mono_cominterop_init (); mono_remoting_init (); @@ -3530,6 +3531,7 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, GHashTable *cache; gboolean pinvoke = FALSE; gboolean skip_gc_trans = FALSE; + gboolean pinvoke_not_found = FALSE; gpointer iter; int i; ERROR_DECL (emitted_error); @@ -3678,12 +3680,17 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, return res; } - /* * In AOT mode and embedding scenarios, it is possible that the icall is not * registered in the runtime doing the AOT compilation. */ - if (!piinfo->addr && !aot) { +#ifdef ENABLE_NETCORE + /* Handled at runtime */ + pinvoke_not_found = !pinvoke && !piinfo->addr && !aot; +#else + pinvoke_not_found = !piinfo->addr && !aot; +#endif + if (pinvoke_not_found) { /* if there's no code but the error isn't set, just use a fairly generic exception. */ if (is_ok (emitted_error)) mono_error_set_generic_error (emitted_error, "System", "MissingMethodException", ""); @@ -3702,8 +3709,6 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, return res; } - g_assert (is_ok (emitted_error)); - /* internal calls: we simply push all arguments and call the method (no conversions) */ if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME) && !pinvoke) { if (sig->hasthis) @@ -3732,8 +3737,6 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, } g_assert (pinvoke); - if (!aot) - g_assert (piinfo->addr); csig = mono_metadata_signature_dup_full (get_method_image (method), sig); mono_marshal_set_callconv_from_modopt (method, csig, FALSE); @@ -6809,6 +6812,20 @@ mono_marshal_get_type_object (MonoClass *klass) return result; } +gpointer +mono_marshal_lookup_pinvoke (MonoMethod *method) +{ + ERROR_DECL (error); + gpointer addr; + + g_assert (method); + addr = mono_lookup_pinvoke_call_internal (method, error); + if (!addr) + g_assert (!is_ok (error)); + mono_error_set_pending_exception (error); + return addr; +} + void mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans) { diff --git a/mono/metadata/marshal.h b/mono/metadata/marshal.h index 0d35659368cb..18e237363459 100644 --- a/mono/metadata/marshal.h +++ b/mono/metadata/marshal.h @@ -605,6 +605,10 @@ mono_marshal_need_free (MonoType *t, MonoMethodPInvoke *piinfo, MonoMarshalSpec ICALL_EXTERN_C MonoObject* mono_marshal_get_type_object (MonoClass *klass); +ICALL_EXTERN_C +gpointer +mono_marshal_lookup_pinvoke (MonoMethod *method); + ICALL_EXPORT guint32 ves_icall_System_Runtime_InteropServices_Marshal_GetLastWin32Error (void); diff --git a/mono/metadata/metadata-internals.h b/mono/metadata/metadata-internals.h index 34ef1d307d4b..4767debd6603 100644 --- a/mono/metadata/metadata-internals.h +++ b/mono/metadata/metadata-internals.h @@ -216,7 +216,7 @@ struct _MonoAssembly { * the additional reference, they can be freed at any time. * The ref_count is initially 0. */ - int ref_count; /* use atomic operations only */ + gint32 ref_count; /* use atomic operations only */ char *basedir; MonoAssemblyName aname; MonoImage *image; @@ -1011,10 +1011,14 @@ gboolean mono_metadata_generic_param_equal (MonoGenericParam *p1, MonoGenericParam *p2); void mono_dynamic_stream_reset (MonoDynamicStream* stream); -MONO_API void mono_assembly_addref (MonoAssembly *assembly); void mono_assembly_load_friends (MonoAssembly* ass); gboolean mono_assembly_has_skip_verification (MonoAssembly* ass); +MONO_API gint32 +mono_assembly_addref (MonoAssembly *assembly); +gint32 +mono_assembly_decref (MonoAssembly *assembly); + void mono_assembly_release_gc_roots (MonoAssembly *assembly); gboolean mono_assembly_close_except_image_pools (MonoAssembly *assembly); void mono_assembly_close_finish (MonoAssembly *assembly); @@ -1104,7 +1108,7 @@ MonoImage *mono_image_open_raw (MonoAssemblyLoadContext *alc, const char *fname, MonoImage *mono_image_open_metadata_only (MonoAssemblyLoadContext *alc, const char *fname, MonoImageOpenStatus *status); -MonoImage *mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only, const char *name); +MonoImage *mono_image_open_from_data_internal (MonoAssemblyLoadContext *alc, char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only, const char *name, const char *filename); MonoException *mono_get_exception_field_access_msg (const char *msg); diff --git a/mono/metadata/mono-private-unstable.h b/mono/metadata/mono-private-unstable.h index c7d2531a7c74..002b8193ac75 100644 --- a/mono/metadata/mono-private-unstable.h +++ b/mono/metadata/mono-private-unstable.h @@ -19,6 +19,9 @@ typedef MonoGCHandle MonoAssemblyLoadContextGCHandle; MONO_API MONO_RT_EXTERNAL_ONLY MonoAssembly * mono_assembly_load_full_alc (MonoAssemblyLoadContextGCHandle alc_gchandle, MonoAssemblyName *aname, const char *basedir, MonoImageOpenStatus *status); +MONO_API MONO_RT_EXTERNAL_ONLY MonoImage * +mono_image_open_from_data_alc (MonoAssemblyLoadContextGCHandle alc_gchandle, char *data, uint32_t data_len, mono_bool need_copy, MonoImageOpenStatus *status, const char *name); + typedef MonoAssembly * (*MonoAssemblyPreLoadFuncV3) (MonoAssemblyLoadContextGCHandle alc_gchandle, MonoAssemblyName *aname, char **assemblies_path, void *user_data, MonoError *error); MONO_API MONO_RT_EXTERNAL_ONLY void diff --git a/mono/metadata/native-library.c b/mono/metadata/native-library.c index ff2c6cc8d647..f53b8d01d78f 100644 --- a/mono/metadata/native-library.c +++ b/mono/metadata/native-library.c @@ -28,8 +28,10 @@ typedef enum DLLIMPORTSEARCHPATH_SAFE_DIRECTORIES = 0x1000, DLLIMPORTSEARCHPATH_ASSEMBLY_DIRECTORY = 0x2, // search the assembly directory first regardless of platform, not passed on to LoadLibraryEx } DllImportSearchPath; -//static const int DLLIMPORTSEARCHPATH_LOADLIBRARY_FLAG_MASK = DLLIMPORTSEARCHPATH_USE_DLL_DIRECTORY_FOR_DEPENDENCIES | DLLIMPORTSEARCHPATH_APPLICATION_DIRECTORY | -// DLLIMPORTSEARCHPATH_USER_DIRECTORIES | DLLIMPORTSEARCHPATH_SYSTEM32 | DLLIMPORTSEARCHPATH_SAFE_DIRECTORIES; +#ifdef HOST_WIN32 +static const int DLLIMPORTSEARCHPATH_LOADLIBRARY_FLAG_MASK = DLLIMPORTSEARCHPATH_USE_DLL_DIRECTORY_FOR_DEPENDENCIES | DLLIMPORTSEARCHPATH_APPLICATION_DIRECTORY | + DLLIMPORTSEARCHPATH_USER_DIRECTORIES | DLLIMPORTSEARCHPATH_SYSTEM32 | DLLIMPORTSEARCHPATH_SAFE_DIRECTORIES; +#endif // This lock may be taken within an ALC lock, and should never be the other way around. static MonoCoopMutex native_library_module_lock; @@ -490,18 +492,28 @@ netcore_check_blocklist (MonoDl *module) return g_hash_table_contains (native_library_module_blocklist, module); } +static int +convert_dllimport_flags (int flags) +{ +#ifdef HOST_WIN32 + return flags & DLLIMPORTSEARCHPATH_LOADLIBRARY_FLAG_MASK; +#else + // DllImportSearchPath is Windows-only, other than DLLIMPORTSEARCHPATH_ASSEMBLY_DIRECTORY + return 0; +#endif +} + static MonoDl * -netcore_probe_for_module_variations (const char *mdirname, const char *file_name) +netcore_probe_for_module_variations (const char *mdirname, const char *file_name, int raw_flags) { void *iter = NULL; char *full_name; MonoDl *module = NULL; - // This does not actually mirror CoreCLR's algorithm; if that becomes a problem, potentially use theirs // FIXME: this appears to search *.dylib twice for some reason while ((full_name = mono_dl_build_path (mdirname, file_name, &iter)) && module == NULL) { char *error_msg; - module = mono_dl_open (full_name, MONO_DL_LAZY, &error_msg); + module = mono_dl_open_full (full_name, MONO_DL_LAZY, raw_flags, &error_msg); if (!module) { mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "DllImport error loading library '%s': '%s'.", full_name, error_msg); g_free (error_msg); @@ -517,19 +529,23 @@ static MonoDl * netcore_probe_for_module (MonoImage *image, const char *file_name, int flags) { MonoDl *module = NULL; + int lflags = convert_dllimport_flags (flags); + + // TODO: this algorithm doesn't quite match CoreCLR, so respecting DLLIMPORTSEARCHPATH_LEGACY_BEHAVIOR makes little sense + // If the difference becomes a problem, overhaul this algorithm to match theirs exactly // Try without any path additions - module = netcore_probe_for_module_variations (NULL, file_name); + module = netcore_probe_for_module_variations (NULL, file_name, lflags); // Check the NATIVE_DLL_SEARCH_DIRECTORIES for (int i = 0; i < pinvoke_search_directories_count && module == NULL; ++i) - module = netcore_probe_for_module_variations (pinvoke_search_directories[i], file_name); + module = netcore_probe_for_module_variations (pinvoke_search_directories[i], file_name, lflags); // Check the assembly directory if the search flag is set and the image exists if (flags & DLLIMPORTSEARCHPATH_ASSEMBLY_DIRECTORY && image != NULL && module == NULL) { char *mdirname = g_path_get_dirname (image->filename); if (mdirname) - module = netcore_probe_for_module_variations (mdirname, file_name); + module = netcore_probe_for_module_variations (mdirname, file_name, lflags); g_free (mdirname); } @@ -772,6 +788,8 @@ netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, c g_free (error_msg); } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via __Internal: '%s'.", scope); + return module; } @@ -796,16 +814,22 @@ netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, c alc_pinvoke_lock (alc); module = netcore_check_alc_cache (alc, scope); alc_pinvoke_unlock (alc); - if (module) + if (module) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found in the active ALC cache: '%s'.", scope); goto leave; + } module = (MonoDl *)netcore_resolve_with_dll_import_resolver_nofail (alc, assembly, scope, flags); - if (module) + if (module) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via DllImportResolver: '%s'.", scope); goto add_to_alc_cache; + } module = (MonoDl *)netcore_resolve_with_load_nofail (alc, scope); - if (module) + if (module) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via LoadUnmanagedDll: '%s'.", scope); goto add_to_alc_cache; + } MONO_ENTER_GC_SAFE; mono_global_loader_data_lock (); @@ -814,18 +838,24 @@ netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, c MONO_ENTER_GC_SAFE; mono_global_loader_data_unlock (); MONO_EXIT_GC_SAFE; - if (module) + if (module) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found in the global cache: '%s'.", scope); goto add_to_alc_cache; + } module = netcore_probe_for_module (image, scope, flags); - if (module) + if (module) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via filesystem probing: '%s'.", scope); goto add_to_global_cache; + } /* As this is last chance, I've opted not to put it in a cache, but that is not necessarily the correct decision. * It is rather convenient here, however, because it means the global cache will only be populated by libraries * resolved via netcore_probe_for_module and not NativeLibrary, eliminating potential races/conflicts. */ module = netcore_resolve_with_resolving_event_nofail (alc, assembly, scope); + if (module) + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via the Resolving event: '%s'.", scope); goto leave; add_to_global_cache: @@ -1293,6 +1323,13 @@ lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_ou if (strcmp (new_scope, "QCall") == 0) { piinfo->addr = mono_lookup_pinvoke_qcall_internal (method, status_out); + if (!piinfo->addr) { + mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_DLLIMPORT, + "Unable to find qcall for '%s'.", + new_import); + status_out->err_code = LOOKUP_PINVOKE_ERR_NO_SYM; + status_out->err_arg = g_strdup (new_import); + } return piinfo->addr; } diff --git a/mono/metadata/object-internals.h b/mono/metadata/object-internals.h index ff255bbbdc47..0175b9134d3b 100644 --- a/mono/metadata/object-internals.h +++ b/mono/metadata/object-internals.h @@ -1663,7 +1663,7 @@ typedef struct { MonoEvent *resolving; MonoEvent *unloading; MonoString *name; - gpointer *native_assembly_load_context; + MonoAssemblyLoadContext *native_assembly_load_context; gint64 id; gint32 internal_state; MonoBoolean is_collectible; diff --git a/mono/metadata/object.c b/mono/metadata/object.c index 140c91e9de62..6925708cfcfc 100644 --- a/mono/metadata/object.c +++ b/mono/metadata/object.c @@ -5446,10 +5446,11 @@ mono_runtime_try_exec_main (MonoMethod *method, MonoArray *args, MonoObject **ex * On failure sets @error and returns NULL. */ static gpointer -invoke_array_extract_argument (MonoArray *params, int i, MonoType *t, gboolean* has_byref_nullables, MonoError *error) +invoke_array_extract_argument (MonoArray *params, int i, MonoType *t, MonoObject **pa_obj, gboolean* has_byref_nullables, MonoError *error) { MonoType *t_orig = t; gpointer result = NULL; + *pa_obj = NULL; error_init (error); again: switch (t->type) { @@ -5470,7 +5471,8 @@ invoke_array_extract_argument (MonoArray *params, int i, MonoType *t, gboolean* case MONO_TYPE_VALUETYPE: if (t->type == MONO_TYPE_VALUETYPE && mono_class_is_nullable (mono_class_from_mono_type_internal (t_orig))) { /* The runtime invoke wrapper needs the original boxed vtype, it does handle byref values as well. */ - result = mono_array_get_internal (params, MonoObject*, i); + *pa_obj = mono_array_get_internal (params, MonoObject*, i); + result = *pa_obj; if (t->byref) *has_byref_nullables = TRUE; } else { @@ -5496,8 +5498,8 @@ invoke_array_extract_argument (MonoArray *params, int i, MonoType *t, gboolean* return_val_if_nok (error, NULL); mono_array_setref_internal (params, i, copy); } - - result = mono_object_unbox_internal (mono_array_get_internal (params, MonoObject*, i)); + *pa_obj = mono_array_get_internal (params, MonoObject*, i); + result = mono_object_unbox_internal (*pa_obj); if (!t->byref && was_null) mono_array_setref_internal (params, i, NULL); } @@ -5507,11 +5509,13 @@ invoke_array_extract_argument (MonoArray *params, int i, MonoType *t, gboolean* case MONO_TYPE_CLASS: case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: - if (t->byref) + if (t->byref) { result = mono_array_addr_internal (params, MonoObject*, i); // FIXME: I need to check this code path - else - result = mono_array_get_internal (params, MonoObject*, i); + } else { + *pa_obj = mono_array_get_internal (params, MonoObject*, i); + result = *pa_obj; + } break; case MONO_TYPE_GENERICINST: if (t->byref) @@ -5680,12 +5684,13 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, MonoObject **exc, MonoError *error) { MONO_REQ_GC_UNSAFE_MODE; + HANDLE_FUNCTION_ENTER (); error_init (error); MonoMethodSignature *sig = mono_method_signature_internal (method); gpointer *pa = NULL; - MonoObject *res; + MonoObject *res = NULL; int i; gboolean has_byref_nullables = FALSE; @@ -5693,8 +5698,11 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, pa = g_newa (gpointer, mono_array_length_internal (params)); for (i = 0; i < mono_array_length_internal (params); i++) { MonoType *t = sig->params [i]; - pa [i] = invoke_array_extract_argument (params, i, t, &has_byref_nullables, error); - return_val_if_nok (error, NULL); + MonoObject *pa_obj; + pa [i] = invoke_array_extract_argument (params, i, t, &pa_obj, &has_byref_nullables, error); + if (pa_obj) + MONO_HANDLE_PIN (pa_obj); + goto_if_nok (error, exit_null); } } @@ -5705,16 +5713,18 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, /* Need to create a boxed vtype instead */ g_assert (!obj); - if (!params) - return NULL; - else { - return mono_value_box_checked (mono_domain_get (), m_class_get_cast_class (method->klass), pa [0], error); + if (!params) { + goto_if_nok (error, exit_null); + } else { + res = mono_value_box_checked (mono_domain_get (), m_class_get_cast_class (method->klass), pa [0], error); + goto exit; } } if (!obj) { - obj = mono_object_new_checked (mono_domain_get (), method->klass, error); - return_val_if_nok (error, NULL); + MonoObjectHandle obj_h = mono_object_new_handle (mono_domain_get (), method->klass, error); + goto_if_nok (error, exit_null); + obj = MONO_HANDLE_RAW (obj_h); g_assert (obj); /*maybe we should raise a TLE instead?*/ #ifndef DISABLE_REMOTING if (mono_object_is_transparent_proxy (obj)) { @@ -5727,8 +5737,9 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, else o = obj; } else if (m_class_is_valuetype (method->klass)) { - obj = mono_value_box_checked (mono_domain_get (), method->klass, obj, error); - return_val_if_nok (error, NULL); + MonoObjectHandle obj_h = mono_value_box_handle (mono_domain_get (), method->klass, obj, error); + goto_if_nok (error, exit_null); + obj = MONO_HANDLE_RAW (obj_h); } if (exc) { @@ -5737,20 +5748,20 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, mono_runtime_invoke_checked (method, o, pa, error); } - return (MonoObject *)obj; + res = (MonoObject*)obj; } else { if (mono_class_is_nullable (method->klass)) { if (method->flags & METHOD_ATTRIBUTE_STATIC) { obj = NULL; } else { - MonoObject *nullable; /* Convert the unboxed vtype into a Nullable structure */ - nullable = mono_object_new_checked (mono_domain_get (), method->klass, error); - return_val_if_nok (error, NULL); + MonoObjectHandle nullable_h = mono_object_new_handle (mono_domain_get (), method->klass, error); + goto_if_nok (error, exit_null); + MonoObject* nullable = MONO_HANDLE_RAW (nullable_h); - MonoObject *boxed = mono_value_box_checked (mono_domain_get (), m_class_get_cast_class (method->klass), obj, error); - return_val_if_nok (error, NULL); - mono_nullable_init ((guint8 *)mono_object_unbox_internal (nullable), boxed, method->klass); + MonoObjectHandle boxed_h = mono_value_box_handle (mono_domain_get (), m_class_get_cast_class (method->klass), obj, error); + goto_if_nok (error, exit_null); + mono_nullable_init ((guint8 *)mono_object_unbox_internal (nullable), MONO_HANDLE_RAW (boxed_h), method->klass); obj = mono_object_unbox_internal (nullable); } } @@ -5761,7 +5772,8 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, } else { res = mono_runtime_invoke_checked (method, obj, pa, error); } - return_val_if_nok (error, NULL); + MONO_HANDLE_PIN (res); + goto_if_nok (error, exit_null); if (sig->ret->type == MONO_TYPE_PTR) { MonoClass *pointer_class; @@ -5790,12 +5802,14 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, // byref is already unboxed by the invoke code MonoType *tmpret = mono_metadata_type_dup (NULL, sig->ret); tmpret->byref = FALSE; - box_args [1] = mono_type_get_object_checked (mono_domain_get (), tmpret, error); + MonoReflectionTypeHandle type_h = mono_type_get_object_handle (mono_domain_get (), tmpret, error); + box_args [1] = MONO_HANDLE_RAW (type_h); mono_metadata_free_type (tmpret); } else { - box_args [1] = mono_type_get_object_checked (mono_domain_get (), sig->ret, error); + MonoReflectionTypeHandle type_h = mono_type_get_object_handle (mono_domain_get (), sig->ret, error); + box_args [1] = MONO_HANDLE_RAW (type_h); } - return_val_if_nok (error, NULL); + goto_if_nok (error, exit_null); res = mono_runtime_try_invoke (box_method, NULL, box_args, &box_exc, error); g_assert (box_exc == NULL); @@ -5815,9 +5829,12 @@ mono_runtime_try_invoke_array (MonoMethod *method, void *obj, MonoArray *params, mono_array_setref_internal (params, i, pa [i]); } } - - return res; } + goto exit; +exit_null: + res = NULL; +exit: + HANDLE_FUNCTION_RETURN_VAL (res); } // FIXME these will move to header soon diff --git a/mono/metadata/reflection.c b/mono/metadata/reflection.c index c28ce829a499..49c907829eb7 100644 --- a/mono/metadata/reflection.c +++ b/mono/metadata/reflection.c @@ -570,7 +570,7 @@ mono_type_get_object_checked (MonoDomain *domain, MonoType *type, MonoError *err res->type = type; mono_g_hash_table_insert_internal (domain->type_hash, type, res); - if (type->type == MONO_TYPE_VOID) + if (type->type == MONO_TYPE_VOID && !type->byref) domain->typeof_void = (MonoObject*)res; mono_domain_unlock (domain); @@ -3131,7 +3131,11 @@ mono_reflection_call_is_assignable_to (MonoClass *klass, MonoClass *oklass, Mono error_init (error); if (method == NULL) { +#ifdef ENABLE_NETCORE + method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableToInternal", 1, 0, error); +#else method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableTo", 1, 0, error); +#endif mono_error_assert_ok (error); g_assert (method); } diff --git a/mono/metadata/sgen-client-mono.h b/mono/metadata/sgen-client-mono.h index 0023ece4b045..211d9ed55498 100644 --- a/mono/metadata/sgen-client-mono.h +++ b/mono/metadata/sgen-client-mono.h @@ -722,6 +722,7 @@ gboolean sgen_is_critical_method (MonoMethod *method); void sgen_set_use_managed_allocator (gboolean flag); gboolean sgen_is_managed_allocator (MonoMethod *method); gboolean sgen_has_managed_allocator (void); +void sgen_disable_native_stack_scan (void); void sgen_scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type); void sgen_null_links_for_domain (MonoDomain *domain); diff --git a/mono/metadata/sgen-mono.c b/mono/metadata/sgen-mono.c index ca00a3066b7a..2a434422c4f7 100644 --- a/mono/metadata/sgen-mono.c +++ b/mono/metadata/sgen-mono.c @@ -993,6 +993,7 @@ static MonoMethod* alloc_method_cache [ATYPE_NUM]; static MonoMethod* slowpath_alloc_method_cache [ATYPE_NUM]; static MonoMethod* profiler_alloc_method_cache [ATYPE_NUM]; static gboolean use_managed_allocator = TRUE; +static gboolean debug_coop_no_stack_scan; #ifdef MANAGED_ALLOCATION /* FIXME: Do this in the JIT, where specialized allocation sequences can be created @@ -1122,6 +1123,12 @@ sgen_set_use_managed_allocator (gboolean flag) use_managed_allocator = flag; } +void +sgen_disable_native_stack_scan (void) +{ + debug_coop_no_stack_scan = TRUE; +} + MonoMethod* mono_gc_get_managed_allocator_by_type (int atype, ManagedAllocatorVariant variant) { @@ -2361,30 +2368,32 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p aligned_stack_start = get_aligned_stack_start (info); g_assert (info->client_info.suspend_done); - SGEN_LOG (3, "Scanning thread %p, range: %p-%p, size: %" G_GSIZE_FORMAT "d, pinned=%" G_GSIZE_FORMAT "d", info, info->client_info.stack_start, info->client_info.info.stack_end, (char*)info->client_info.info.stack_end - (char*)info->client_info.stack_start, sgen_get_pinned_count ()); - if (mono_gc_get_gc_callbacks ()->thread_mark_func && !conservative_stack_mark) { - mono_gc_get_gc_callbacks ()->thread_mark_func (info->client_info.runtime_data, (guint8 *)aligned_stack_start, (guint8 *)info->client_info.info.stack_end, precise, &ctx); - } else if (!precise) { - if (!conservative_stack_mark) { - fprintf (stderr, "Precise stack mark not supported - disabling.\n"); - conservative_stack_mark = TRUE; + if (!debug_coop_no_stack_scan) { + SGEN_LOG (3, "Scanning thread %p, range: %p-%p, size: %" G_GSIZE_FORMAT "d, pinned=%" G_GSIZE_FORMAT "d", info, info->client_info.stack_start, info->client_info.info.stack_end, (char*)info->client_info.info.stack_end - (char*)info->client_info.stack_start, sgen_get_pinned_count ()); + if (mono_gc_get_gc_callbacks ()->thread_mark_func && !conservative_stack_mark) { + mono_gc_get_gc_callbacks ()->thread_mark_func (info->client_info.runtime_data, (guint8 *)aligned_stack_start, (guint8 *)info->client_info.info.stack_end, precise, &ctx); + } else if (!precise) { + if (!conservative_stack_mark) { + fprintf (stderr, "Precise stack mark not supported - disabling.\n"); + conservative_stack_mark = TRUE; + } + //FIXME we should eventually use the new stack_mark from coop + sgen_conservatively_pin_objects_from ((void **)aligned_stack_start, (void **)info->client_info.info.stack_end, start_nursery, end_nursery, PIN_TYPE_STACK); } - //FIXME we should eventually use the new stack_mark from coop - sgen_conservatively_pin_objects_from ((void **)aligned_stack_start, (void **)info->client_info.info.stack_end, start_nursery, end_nursery, PIN_TYPE_STACK); - } - if (!precise) { - sgen_conservatively_pin_objects_from ((void**)&info->client_info.ctx, (void**)(&info->client_info.ctx + 1), - start_nursery, end_nursery, PIN_TYPE_STACK); - - { - // This is used on Coop GC for platforms where we cannot get the data for individual registers. - // We force a spill of all registers into the stack and pass a chunk of data into sgen. - //FIXME under coop, for now, what we need to ensure is that we scan any extra memory from info->client_info.info.stack_end to stack_mark - MonoThreadUnwindState *state = &info->client_info.info.thread_saved_state [SELF_SUSPEND_STATE_INDEX]; - if (state && state->gc_stackdata) { - sgen_conservatively_pin_objects_from ((void **)state->gc_stackdata, (void**)((char*)state->gc_stackdata + state->gc_stackdata_size), - start_nursery, end_nursery, PIN_TYPE_STACK); + if (!precise) { + sgen_conservatively_pin_objects_from ((void**)&info->client_info.ctx, (void**)(&info->client_info.ctx + 1), + start_nursery, end_nursery, PIN_TYPE_STACK); + + { + // This is used on Coop GC for platforms where we cannot get the data for individual registers. + // We force a spill of all registers into the stack and pass a chunk of data into sgen. + //FIXME under coop, for now, what we need to ensure is that we scan any extra memory from info->client_info.info.stack_end to stack_mark + MonoThreadUnwindState *state = &info->client_info.info.thread_saved_state [SELF_SUSPEND_STATE_INDEX]; + if (state && state->gc_stackdata) { + sgen_conservatively_pin_objects_from ((void **)state->gc_stackdata, (void**)((char*)state->gc_stackdata + state->gc_stackdata_size), + start_nursery, end_nursery, PIN_TYPE_STACK); + } } } } diff --git a/mono/metadata/sre-save.c b/mono/metadata/sre-save.c index f2371b511b65..e61514de08e7 100644 --- a/mono/metadata/sre-save.c +++ b/mono/metadata/sre-save.c @@ -630,17 +630,22 @@ static gboolean mono_image_get_method_info (MonoReflectionMethodBuilder *mb, MonoDynamicImage *assembly, MonoError *error) { MONO_REQ_GC_UNSAFE_MODE; + /* We need to clear handles for rmb fields created in mono_reflection_methodbuilder_from_method_builder */ + HANDLE_FUNCTION_ENTER (); MonoDynamicTable *table; guint32 *values; ReflectionMethodBuilder rmb; int i; + gboolean ret = TRUE; error_init (error); if (!mono_reflection_methodbuilder_from_method_builder (&rmb, mb, error) || - !mono_image_basic_method (&rmb, assembly, error)) - return FALSE; + !mono_image_basic_method (&rmb, assembly, error)) { + ret = FALSE; + goto exit; + } mb->table_idx = *rmb.table_idx; @@ -685,26 +690,33 @@ mono_image_get_method_info (MonoReflectionMethodBuilder *mb, MonoDynamicImage *a (MonoReflectionGenericParam *)mono_array_get_internal (mb->generic_params, gpointer, i), owner, assembly); } } - - return TRUE; +exit: + HANDLE_FUNCTION_RETURN_VAL (ret); } static gboolean mono_image_get_ctor_info (MonoDomain *domain, MonoReflectionCtorBuilder *mb, MonoDynamicImage *assembly, MonoError *error) { + /* We need to clear handles for rmb fields created in mono_reflection_methodbuilder_from_ctor_builder */ + HANDLE_FUNCTION_ENTER (); MONO_REQ_GC_UNSAFE_MODE; + gboolean ret = TRUE; ReflectionMethodBuilder rmb; - if (!mono_reflection_methodbuilder_from_ctor_builder (&rmb, mb, error)) - return FALSE; + if (!mono_reflection_methodbuilder_from_ctor_builder (&rmb, mb, error)) { + ret = FALSE; + goto exit; + } - if (!mono_image_basic_method (&rmb, assembly, error)) - return FALSE; + if (!mono_image_basic_method (&rmb, assembly, error)) { + ret = FALSE; + goto exit; + } mb->table_idx = *rmb.table_idx; - - return TRUE; +exit: + HANDLE_FUNCTION_RETURN_VAL (ret); } #endif diff --git a/mono/metadata/sre.c b/mono/metadata/sre.c index 6881617fedc5..d2ee825173f6 100644 --- a/mono/metadata/sre.c +++ b/mono/metadata/sre.c @@ -480,27 +480,39 @@ mono_reflection_methodbuilder_from_method_builder (ReflectionMethodBuilder *rmb, memset (rmb, 0, sizeof (ReflectionMethodBuilder)); rmb->ilgen = mb->ilgen; + MONO_HANDLE_PIN (rmb->ilgen); rmb->rtype = (MonoReflectionType*)mb->rtype; - return_val_if_nok (error, FALSE); + MONO_HANDLE_PIN (rmb->rtype); rmb->parameters = mb->parameters; + MONO_HANDLE_PIN (rmb->parameters); rmb->generic_params = mb->generic_params; + MONO_HANDLE_PIN (rmb->generic_params); rmb->generic_container = mb->generic_container; rmb->opt_types = NULL; rmb->pinfo = mb->pinfo; + MONO_HANDLE_PIN (rmb->pinfo); rmb->attrs = mb->attrs; rmb->iattrs = mb->iattrs; rmb->call_conv = mb->call_conv; rmb->code = mb->code; + MONO_HANDLE_PIN (rmb->code); rmb->type = mb->type; + MONO_HANDLE_PIN (rmb->type); rmb->name = mb->name; + MONO_HANDLE_PIN (rmb->name); rmb->table_idx = &mb->table_idx; rmb->init_locals = mb->init_locals; rmb->skip_visibility = FALSE; rmb->return_modreq = mb->return_modreq; + MONO_HANDLE_PIN (rmb->return_modreq); rmb->return_modopt = mb->return_modopt; + MONO_HANDLE_PIN (rmb->return_modopt); rmb->param_modreq = mb->param_modreq; + MONO_HANDLE_PIN (rmb->param_modreq); rmb->param_modopt = mb->param_modopt; + MONO_HANDLE_PIN (rmb->param_modopt); rmb->permissions = mb->permissions; + MONO_HANDLE_PIN (rmb->permissions); rmb->mhandle = mb->mhandle; rmb->nrefs = 0; rmb->refs = NULL; @@ -510,7 +522,9 @@ mono_reflection_methodbuilder_from_method_builder (ReflectionMethodBuilder *rmb, rmb->extra_flags = mb->extra_flags; rmb->native_cc = mb->native_cc; rmb->dllentry = mb->dllentry; + MONO_HANDLE_PIN (rmb->dllentry); rmb->dll = mb->dll; + MONO_HANDLE_PIN (rmb->dll); } return TRUE; @@ -528,28 +542,37 @@ mono_reflection_methodbuilder_from_ctor_builder (ReflectionMethodBuilder *rmb, M memset (rmb, 0, sizeof (ReflectionMethodBuilder)); rmb->ilgen = mb->ilgen; + MONO_HANDLE_PIN (rmb->ilgen); rmb->rtype = mono_type_get_object_checked (mono_domain_get (), mono_get_void_type (), error); return_val_if_nok (error, FALSE); + MONO_HANDLE_PIN (rmb->rtype); rmb->parameters = mb->parameters; + MONO_HANDLE_PIN (rmb->parameters); rmb->generic_params = NULL; rmb->generic_container = NULL; rmb->opt_types = NULL; rmb->pinfo = mb->pinfo; + MONO_HANDLE_PIN (rmb->pinfo); rmb->attrs = mb->attrs; rmb->iattrs = mb->iattrs; rmb->call_conv = mb->call_conv; rmb->code = NULL; rmb->type = mb->type; + MONO_HANDLE_PIN (rmb->type); rmb->name = mono_string_new_checked (mono_domain_get (), name, error); return_val_if_nok (error, FALSE); + MONO_HANDLE_PIN (rmb->name); rmb->table_idx = &mb->table_idx; rmb->init_locals = mb->init_locals; rmb->skip_visibility = FALSE; rmb->return_modreq = NULL; rmb->return_modopt = NULL; rmb->param_modreq = mb->param_modreq; + MONO_HANDLE_PIN (rmb->param_modreq); rmb->param_modopt = mb->param_modopt; + MONO_HANDLE_PIN (rmb->param_modopt); rmb->permissions = mb->permissions; + MONO_HANDLE_PIN (rmb->permissions); rmb->mhandle = mb->mhandle; rmb->nrefs = 0; rmb->refs = NULL; @@ -565,8 +588,11 @@ reflection_methodbuilder_from_dynamic_method (ReflectionMethodBuilder *rmb, Mono memset (rmb, 0, sizeof (ReflectionMethodBuilder)); rmb->ilgen = mb->ilgen; + MONO_HANDLE_PIN (rmb->ilgen); rmb->rtype = mb->rtype; + MONO_HANDLE_PIN (rmb->type); rmb->parameters = mb->parameters; + MONO_HANDLE_PIN (rmb->parameters); rmb->generic_params = NULL; rmb->generic_container = NULL; rmb->opt_types = NULL; @@ -576,7 +602,9 @@ reflection_methodbuilder_from_dynamic_method (ReflectionMethodBuilder *rmb, Mono rmb->call_conv = mb->call_conv; rmb->code = NULL; rmb->type = (MonoObject *) mb->owner; + MONO_HANDLE_PIN (rmb->type); rmb->name = mb->name; + MONO_HANDLE_PIN (rmb->name); rmb->table_idx = NULL; rmb->init_locals = mb->init_locals; rmb->skip_visibility = mb->skip_visibility; @@ -3262,23 +3290,26 @@ reflection_methodbuilder_to_mono_method (MonoClass *klass, static MonoMethod* ctorbuilder_to_mono_method (MonoClass *klass, MonoReflectionCtorBuilder* mb, MonoError *error) { + /* We need to clear handles for rmb fields created in mono_reflection_methodbuilder_from_ctor_builder */ + HANDLE_FUNCTION_ENTER (); ReflectionMethodBuilder rmb; MonoMethodSignature *sig; + MonoMethod *ret; mono_loader_lock (); if (!mono_reflection_methodbuilder_from_ctor_builder (&rmb, mb, error)) { mono_loader_unlock (); - return NULL; + goto exit_null; } g_assert (klass->image != NULL); sig = ctor_builder_to_signature_raw (klass->image, mb, error); /* FIXME use handles */ mono_loader_unlock (); - return_val_if_nok (error, NULL); + goto_if_nok (error, exit_null); mb->mhandle = reflection_methodbuilder_to_mono_method (klass, &rmb, sig, error); - return_val_if_nok (error, NULL); + goto_if_nok (error, exit_null); mono_save_custom_attrs (klass->image, mb->mhandle, mb->cattrs); if (!((MonoDynamicImage*)(MonoDynamicImage*)klass->image)->save) { @@ -3286,14 +3317,22 @@ ctorbuilder_to_mono_method (MonoClass *klass, MonoReflectionCtorBuilder* mb, Mon mb->ilgen = NULL; } - return mb->mhandle; + ret = mb->mhandle; + goto exit; +exit_null: + ret = NULL; +exit: + HANDLE_FUNCTION_RETURN_VAL (ret); } static MonoMethod* methodbuilder_to_mono_method (MonoClass *klass, MonoReflectionMethodBuilderHandle ref_mb, MonoError *error) { + /* We need to clear handles for rmb fields created in mono_reflection_methodbuilder_from_method_builder */ + HANDLE_FUNCTION_ENTER (); ReflectionMethodBuilder rmb; MonoMethodSignature *sig; + MonoMethod *ret, *method; error_init (error); @@ -3302,23 +3341,28 @@ methodbuilder_to_mono_method (MonoClass *klass, MonoReflectionMethodBuilderHandl MonoReflectionMethodBuilder *mb = MONO_HANDLE_RAW (ref_mb); /* FIXME use handles */ if (!mono_reflection_methodbuilder_from_method_builder (&rmb, mb, error)) { mono_loader_unlock (); - return NULL; + goto exit_null; } g_assert (klass->image != NULL); sig = method_builder_to_signature (klass->image, ref_mb, error); mono_loader_unlock (); - return_val_if_nok (error, NULL); + goto_if_nok (error, exit_null); - MonoMethod *method = reflection_methodbuilder_to_mono_method (klass, &rmb, sig, error); - return_val_if_nok (error, NULL); + method = reflection_methodbuilder_to_mono_method (klass, &rmb, sig, error); + goto_if_nok (error, exit_null); MONO_HANDLE_SETVAL (ref_mb, mhandle, MonoMethod*, method); mono_save_custom_attrs (klass->image, method, mb->cattrs); if (!((MonoDynamicImage*)(MonoDynamicImage*)klass->image)->save) /* ilgen is no longer needed */ mb->ilgen = NULL; - return method; + ret = method; + goto exit; +exit_null: + ret = NULL; +exit: + HANDLE_FUNCTION_RETURN_VAL (ret); } static MonoMethod* @@ -3668,9 +3712,11 @@ typebuilder_setup_one_field (MonoDynamicImage *dynamic_image, MonoClass *klass, if (klass->enumtype && strcmp (field->name, "value__") == 0) // used by enum classes to store the instance value field->type->attrs |= FIELD_ATTRIBUTE_RT_SPECIAL_NAME; - if (!klass->enumtype && !mono_type_get_underlying_type (field->type)) { - mono_class_set_type_load_failure (klass, "Field '%s' is an enum type with a bad underlying type", field->name); - goto leave; + if (!mono_type_get_underlying_type (field->type)) { + if (!(klass->enumtype && mono_metadata_type_equal (field->type, m_class_get_byval_arg (klass)))) { + mono_class_set_type_load_failure (klass, "Field '%s' is an enum type with a bad underlying type", field->name); + goto leave; + } } if ((fb->attrs & FIELD_ATTRIBUTE_HAS_FIELD_RVA) && (rva_data = fb->rva_data)) { @@ -4105,6 +4151,8 @@ free_dynamic_method (void *dynamic_method) static gboolean reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, MonoError *error) { + /* We need to clear handles for rmb fields created in reflection_methodbuilder_from_dynamic_method */ + HANDLE_FUNCTION_ENTER (); MonoReferenceQueue *queue; MonoMethod *handle; DynamicMethodReleaseData *release_data; @@ -4114,6 +4162,9 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono MonoDomain *domain; GSList *l; int i; + gboolean ret = TRUE; + MonoReflectionDynamicMethod *mb; + MonoAssembly *ass = NULL; error_init (error); @@ -4125,9 +4176,9 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono } sig = dynamic_method_to_signature (ref_mb, error); - return_val_if_nok (error, FALSE); + goto_if_nok (error, exit_false); - MonoReflectionDynamicMethod *mb = MONO_HANDLE_RAW (ref_mb); /* FIXME convert reflection_create_dynamic_method to use handles */ + mb = MONO_HANDLE_RAW (ref_mb); /* FIXME convert reflection_create_dynamic_method to use handles */ reflection_methodbuilder_from_dynamic_method (&rmb, mb); /* @@ -4143,6 +4194,7 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono MonoClass *handle_class; gpointer ref; MonoObject *obj = mono_array_get_internal (mb->refs, MonoObject*, i); + MONO_HANDLE_PIN (obj); if (strcmp (obj->vtable->klass->name, "DynamicMethod") == 0) { MonoReflectionDynamicMethod *method = (MonoReflectionDynamicMethod*)obj; @@ -4155,19 +4207,18 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono if (method->mhandle) { ref = method->mhandle; } else { - /* FIXME: GC object stored in unmanaged memory */ ref = method; - /* FIXME: GC object stored in unmanaged memory */ method->referenced_by = g_slist_append (method->referenced_by, mb); } handle_class = mono_defaults.methodhandle_class; } else { MonoException *ex = NULL; ref = mono_reflection_resolve_object (mb->module->image, obj, &handle_class, NULL, error); + /* ref should not be a reference. Otherwise we would need a handle for it */ if (!is_ok (error)) { g_free (rmb.refs); - return FALSE; + goto exit_false; } if (!ref) ex = mono_get_exception_type_load (NULL, NULL); @@ -4177,20 +4228,19 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono if (ex) { g_free (rmb.refs); mono_error_set_exception_instance (error, ex); - return FALSE; + goto exit_false; } } - rmb.refs [i] = ref; /* FIXME: GC object stored in unmanaged memory (change also resolve_object() signature) */ + rmb.refs [i] = ref; rmb.refs [i + 1] = handle_class; } - MonoAssembly *ass = NULL; if (mb->owner) { MonoType *owner_type = mono_reflection_type_get_handle ((MonoReflectionType*)mb->owner, error); if (!is_ok (error)) { g_free (rmb.refs); - return FALSE; + goto exit_false; } klass = mono_class_from_mono_type_internal (owner_type); ass = klass->image->assembly; @@ -4202,7 +4252,7 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono mb->mhandle = handle = reflection_methodbuilder_to_mono_method (klass, &rmb, sig, error); ((MonoDynamicMethod*)handle)->assembly = ass; g_free (rmb.refs); - return_val_if_nok (error, FALSE); + goto_if_nok (error, exit_false); release_data = g_new (DynamicMethodReleaseData, 1); release_data->handle = handle; @@ -4233,7 +4283,11 @@ reflection_create_dynamic_method (MonoReflectionDynamicMethodHandle ref_mb, Mono g_hash_table_insert (domain->method_to_dyn_method, handle, mono_gchandle_new_weakref_internal ((MonoObject *)mb, TRUE)); mono_domain_unlock (domain); - return TRUE; + goto exit; +exit_false: + ret = FALSE; +exit: + HANDLE_FUNCTION_RETURN_VAL (ret); } void diff --git a/mono/metadata/w32file.c b/mono/metadata/w32file.c index 67cc6412c447..bee7f7174009 100644 --- a/mono/metadata/w32file.c +++ b/mono/metadata/w32file.c @@ -913,4 +913,8 @@ mono_filesize_from_fd (int fd) return (gint64)buf.st_size; } +#else + +MONO_EMPTY_SOURCE_FILE (w32file); + #endif diff --git a/mono/metadata/w32process-unix-bsd.c b/mono/metadata/w32process-unix-bsd.c index d09bc9f506ed..e368c19bd8bd 100644 --- a/mono/metadata/w32process-unix-bsd.c +++ b/mono/metadata/w32process-unix-bsd.c @@ -175,6 +175,11 @@ mono_w32process_get_modules (pid_t pid) return g_slist_reverse (ret); } +void +mono_w32process_platform_init_once (void) +{ +} + #else MONO_EMPTY_SOURCE_FILE (w32process_unix_bsd); diff --git a/mono/metadata/w32process-unix-default.c b/mono/metadata/w32process-unix-default.c index e2fda17fb5a0..eb73b89b75da 100644 --- a/mono/metadata/w32process-unix-default.c +++ b/mono/metadata/w32process-unix-default.c @@ -360,4 +360,13 @@ mono_w32process_get_modules (pid_t pid) #endif } +void +mono_w32process_platform_init_once (void) +{ +} + +#else + +MONO_EMPTY_SOURCE_FILE (w32process_unix_default); + #endif diff --git a/mono/metadata/w32process-unix-haiku.c b/mono/metadata/w32process-unix-haiku.c index 0b3c82436c7f..dbd312c8d864 100644 --- a/mono/metadata/w32process-unix-haiku.c +++ b/mono/metadata/w32process-unix-haiku.c @@ -57,6 +57,11 @@ mono_w32process_get_modules (pid_t pid) return g_slist_reverse (ret); } +void +mono_w32process_platform_init_once (void) +{ +} + #else MONO_EMPTY_SOURCE_FILE (w32process_unix_haiku); diff --git a/mono/metadata/w32process-unix-internals.h b/mono/metadata/w32process-unix-internals.h index 7ba5f7e002e1..056177dd22a9 100644 --- a/mono/metadata/w32process-unix-internals.h +++ b/mono/metadata/w32process-unix-internals.h @@ -40,6 +40,9 @@ mono_w32process_get_name (pid_t pid); GSList* mono_w32process_get_modules (pid_t pid); +void +mono_w32process_platform_init_once (void); + static void G_GNUC_UNUSED mono_w32process_module_free (MonoW32ProcessModule *module) { diff --git a/mono/metadata/w32process-unix-osx.c b/mono/metadata/w32process-unix-osx.c index c687f6a70a11..2568a44223e3 100644 --- a/mono/metadata/w32process-unix-osx.c +++ b/mono/metadata/w32process-unix-osx.c @@ -15,6 +15,7 @@ #include #include #include +#include /* sys/resource.h (for rusage) is required when using osx 10.3 (but not 10.4) */ #ifdef __APPLE__ @@ -117,58 +118,128 @@ mono_w32process_get_path (pid_t pid) #endif } -GSList* +struct mono_dyld_image_info +{ + const void *header_addr; + const void *data_section_start; + const void *data_section_end; + const char *name; + guint64 order; +}; + +static guint64 dyld_order = 0; +static GHashTable *images; +static mono_mutex_t images_mutex; + +static int +sort_modules_by_load_order (gconstpointer a, gconstpointer b) +{ + MonoW32ProcessModule *ma = (MonoW32ProcessModule *) a; + MonoW32ProcessModule *mb = (MonoW32ProcessModule *) b; + return ma->inode == mb->inode ? 0 : ma->inode < mb->inode ? -1 : 1; +} + +GSList * mono_w32process_get_modules (pid_t pid) { GSList *ret = NULL; - MonoW32ProcessModule *mod; - guint32 count; - int i = 0; - + MONO_ENTER_GC_SAFE; if (pid != getpid ()) - return NULL; + goto done; - count = _dyld_image_count (); - for (i = 0; i < count; i++) { -#if SIZEOF_VOID_P == 8 - const struct mach_header_64 *hdr; - const struct section_64 *sec; -#else - const struct mach_header *hdr; - const struct section *sec; -#endif - const char *name; - - name = _dyld_get_image_name (i); -#if SIZEOF_VOID_P == 8 - hdr = (const struct mach_header_64*)_dyld_get_image_header (i); - sec = getsectbynamefromheader_64 (hdr, SEG_DATA, SECT_DATA); -#else - hdr = _dyld_get_image_header (i); - sec = getsectbynamefromheader (hdr, SEG_DATA, SECT_DATA); -#endif + GHashTableIter it; + g_hash_table_iter_init (&it, images); - /* Some dynlibs do not have data sections on osx (#533893) */ - if (sec == 0) - continue; + gpointer val; - mod = g_new0 (MonoW32ProcessModule, 1); - mod->address_start = GINT_TO_POINTER (sec->addr); - mod->address_end = GINT_TO_POINTER (sec->addr+sec->size); + mono_os_mutex_lock (&images_mutex); + while (g_hash_table_iter_next (&it, NULL, &val)) { + struct mono_dyld_image_info *info = (struct mono_dyld_image_info *) val; + MonoW32ProcessModule *mod = g_new0 (MonoW32ProcessModule, 1); + mod->address_start = GINT_TO_POINTER (info->data_section_start); + mod->address_end = GINT_TO_POINTER (info->data_section_end); mod->perms = g_strdup ("r--p"); mod->address_offset = 0; - mod->device = makedev (0, 0); - mod->inode = i; - mod->filename = g_strdup (name); - - if (g_slist_find_custom (ret, mod, mono_w32process_module_equals) == NULL) { - ret = g_slist_prepend (ret, mod); - } else { - mono_w32process_module_free (mod); - } + mod->device = 0; + mod->inode = info->order; + mod->filename = g_strdup (info->name); + ret = g_slist_prepend (ret, mod); } + mono_os_mutex_unlock (&images_mutex); + ret = g_slist_sort (ret, &sort_modules_by_load_order); +done: + MONO_EXIT_GC_SAFE; + return ret; +} + +static void +mono_dyld_image_info_free (void *info) +{ + struct mono_dyld_image_info *dinfo = (struct mono_dyld_image_info *) info; + g_free ((void *) dinfo->name); + g_free (dinfo); +} + +static void +image_added (const struct mach_header *hdr32, intptr_t vmaddr_slide) +{ + #if SIZEOF_VOID_P == 8 + const struct mach_header_64 *hdr64 = (const struct mach_header_64 *)hdr32; + const struct section_64 *sec = getsectbynamefromheader_64 (hdr64, SEG_DATA, SECT_DATA); + #else + const struct section *sec = getsectbynamefromheader (hdr32, SEG_DATA, SECT_DATA); + #endif + Dl_info dlinfo; + if (!dladdr (hdr32, &dlinfo)) return; + if (sec == NULL) return; + + mono_os_mutex_lock (&images_mutex); + gpointer found = g_hash_table_lookup (images, (gpointer) hdr32); + mono_os_mutex_unlock (&images_mutex); + + if (found == NULL) { + struct mono_dyld_image_info *info = g_new0 (struct mono_dyld_image_info, 1); + info->header_addr = hdr32; + info->data_section_start = GINT_TO_POINTER (sec->addr); + info->data_section_end = GINT_TO_POINTER (sec->addr + sec->size); + info->name = g_strdup (dlinfo.dli_fname); + info->order = dyld_order; + ++dyld_order; + + mono_os_mutex_lock (&images_mutex); + g_hash_table_insert (images, (gpointer) hdr32, info); + mono_os_mutex_unlock (&images_mutex); + } +} + +static void +image_removed (const struct mach_header *hdr32, intptr_t vmaddr_slide) +{ + mono_os_mutex_lock (&images_mutex); + g_hash_table_remove (images, hdr32); + mono_os_mutex_unlock (&images_mutex); +} - return g_slist_reverse (ret); +void +mono_w32process_platform_init_once (void) +{ + mono_os_mutex_init (&images_mutex); + images = g_hash_table_new_full (NULL, NULL, NULL, &mono_dyld_image_info_free); + + /* Ensure that the functions used within the lock-protected region in + * mono_w32process_get_modules have been loaded, in case these symbols + * are lazily bound. g_new0 and g_strdup will be called by + * _dyld_register_func_for_add_image when it calls image_added with the + * current list of all loaded dynamic libraries + */ + GSList *dummy = g_slist_prepend (NULL, NULL); + g_slist_free (dummy); + GHashTableIter it; + g_hash_table_iter_init (&it, images); + g_hash_table_iter_next (&it, NULL, NULL); + + _dyld_register_func_for_add_image (&image_added); + _dyld_register_func_for_remove_image (&image_removed); } #else diff --git a/mono/metadata/w32process-unix.c b/mono/metadata/w32process-unix.c index 5ce501fd5a81..c1ccbca77c95 100644 --- a/mono/metadata/w32process-unix.c +++ b/mono/metadata/w32process-unix.c @@ -85,6 +85,7 @@ #include #include #include +#include #include #include #include "object-internals.h" @@ -594,6 +595,8 @@ process_set_name (MonoW32HandleProcess *process_handle) } } +static mono_once_t init_state = MONO_ONCE_INIT; + void mono_w32process_init (void) { @@ -615,6 +618,7 @@ mono_w32process_init (void) g_assert (current_process != INVALID_HANDLE_VALUE); mono_coop_mutex_init (&processes_mutex); + mono_once (&init_state, &mono_w32process_platform_init_once); } void diff --git a/mono/metadata/w32socket-internals.h b/mono/metadata/w32socket-internals.h index 6cdb94063b17..0fe5209c321d 100644 --- a/mono/metadata/w32socket-internals.h +++ b/mono/metadata/w32socket-internals.h @@ -64,7 +64,7 @@ SOCKET mono_w32socket_accept (SOCKET s, struct sockaddr *addr, socklen_t *addrlen, gboolean blocking); int -mono_w32socket_connect (SOCKET s, const struct sockaddr *name, int namelen, gboolean blocking); +mono_w32socket_connect (SOCKET s, const struct sockaddr *name, socklen_t namelen, gboolean blocking); int mono_w32socket_recv (SOCKET s, char *buf, int len, int flags, gboolean blocking); diff --git a/mono/metadata/w32socket-unix.c b/mono/metadata/w32socket-unix.c index ac3bd0f85ff2..f5803a42a40b 100644 --- a/mono/metadata/w32socket-unix.c +++ b/mono/metadata/w32socket-unix.c @@ -191,7 +191,7 @@ mono_w32socket_accept (SOCKET sock, struct sockaddr *addr, socklen_t *addrlen, g } int -mono_w32socket_connect (SOCKET sock, const struct sockaddr *addr, int addrlen, gboolean blocking) +mono_w32socket_connect (SOCKET sock, const struct sockaddr *addr, socklen_t addrlen, gboolean blocking) { SocketHandle *sockethandle; gint ret; diff --git a/mono/mini/CMakeLists.txt b/mono/mini/CMakeLists.txt index d98715012547..3d63d99b790f 100644 --- a/mono/mini/CMakeLists.txt +++ b/mono/mini/CMakeLists.txt @@ -1,15 +1,17 @@ -project (mini) +project(mini) -function (addprefix var prefix list) - foreach (i ${list}) - set (f ${f} ${prefix}/${i}) - endforeach () - set (${var} ${f} PARENT_SCOPE) +function(addprefix var prefix list) + foreach(i ${list}) + set(f ${f} ${prefix}/${i}) + endforeach() + set(${var} ${f} PARENT_SCOPE) endfunction() include_directories( ${PROJECT_BINARY_DIR}/ ${PROJECT_BINARY_DIR}/../.. + ${PROJECT_BINARY_DIR}/../../mono/eglib + ${CMAKE_SOURCE_DIR}/ ${PROJECT_SOURCE_DIR}/../ ${PROJECT_SOURCE_DIR}/../eglib ${PROJECT_SOURCE_DIR}/../sgen) @@ -18,672 +20,840 @@ include_directories( # EGLIB/ # -set (eglib_win32_sources - eglib-config.hw - gdate-win32.c gdir-win32.c gfile-win32.c gmisc-win32.c - gmodule-win32.c gmodule-win32-internals.h gtimer-win32.c gunicode-win32.c) +set(eglib_win32_sources + eglib-config.hw + gdate-win32.c gdir-win32.c gfile-win32.c gmisc-win32.c + gmodule-win32.c gmodule-win32-internals.h gtimer-win32.c gunicode-win32.c) -set (eglib_unix_sources - gdate-unix.c gdir-unix.c gfile-unix.c gmisc-unix.c - gmodule-unix.c gtimer-unix.c) +set(eglib_unix_sources + gdate-unix.c gdir-unix.c gfile-unix.c gmisc-unix.c + gmodule-unix.c gtimer-unix.c) -if (HOST_WIN32) -set (eglib_platform_sources ${eglib_win32_sources}) +if(HOST_WIN32) +set(eglib_platform_sources ${eglib_win32_sources}) else() -set (eglib_platform_sources ${eglib_unix_sources}) +set(eglib_platform_sources ${eglib_unix_sources}) endif() -set (eglib_common_sources - eglib-remap.h - sort.frag.h - glib.h - garray.c - gbytearray.c - gerror.c - ghashtable.c - giconv.c - gmem.c - gmodule.h - goutput.c - gqsort.c - gstr.c - gslist.c - gstring.c - gptrarray.c - glist.c - gqueue.c - gpath.c - gshell.c - gspawn.c - gfile.c - gfile-posix.c - gpattern.c - gmarkup.c - gutf8.c - gunicode.c - unicode-data.h) - -addprefix (eglib_sources ../eglib/ "${eglib_platform_sources};${eglib_common_sources}") +set(eglib_common_sources + eglib-remap.h + sort.frag.h + glib.h + garray.c + gbytearray.c + gerror.c + ghashtable.c + giconv.c + gmem.c + gmodule.h + goutput.c + gqsort.c + gstr.c + gslist.c + gstring.c + gptrarray.c + glist.c + gqueue.c + gpath.c + gshell.c + gspawn.c + gfile.c + gfile-posix.c + gpattern.c + gmarkup.c + gutf8.c + gunicode.c + unicode-data.h) + +addprefix(eglib_sources ../eglib/ "${eglib_platform_sources};${eglib_common_sources}") # # UTILS/ # -set (utils_win32_sources - os-event-win32.c - mono-os-wait-win32.c) +set(utils_win32_sources + os-event-win32.c + mono-os-wait-win32.c) -set (utils_unix_sources - os-event-unix.c) +set(utils_unix_sources + os-event-unix.c) -if (HOST_WIN32) -set (utils_platform_sources ${utils_win32_sources}) +if(HOST_WIN32) +set(utils_platform_sources ${utils_win32_sources}) else() -set (utils_platform_sources ${utils_unix_sources}) +set(utils_platform_sources ${utils_unix_sources}) endif() -set (utils_common_sources - mono-md5.c - mono-sha1.c - mono-logger.c - mono-logger-internals.h - mono-codeman.c - dlmalloc.h - dlmalloc.c - mono-counters.c - mono-compiler.h - mono-complex.h - mono-dl.c - mono-dl-windows.c - mono-dl-darwin.c - mono-dl-posix.c - mono-dl-wasm.c - mono-dl.h - mono-dl-windows-internals.h - mono-log-windows.c - mono-log-common.c - mono-log-posix.c - mono-log-android.c - mono-log-darwin.c - mono-log-flight-recorder.c - mono-merp.c - mono-merp.h - mono-state.h - mono-state.c - mono-internal-hash.c - mono-internal-hash.h - mono-io-portability.c - mono-io-portability.h - monobitset.c - mono-filemap.c - mono-math-c.c - mono-mmap.c - mono-mmap-windows.c - mono-mmap.h - mono-mmap-internals.h - mono-mmap-windows-internals.h - mono-os-mutex.h - mono-os-mutex.c - mono-flight-recorder.h - mono-flight-recorder.c - mono-os-wait.h - mono-coop-mutex.h - mono-once.h - mono-lazy-init.h - mono-networkinterfaces.c - mono-networkinterfaces.h - mono-proclib.c - mono-proclib-windows.c - mono-proclib.h - mono-proclib-windows-internals.h - mono-publib.c - mono-jemalloc.c - mono-string.h - mono-time.c - mono-time.h - strenc.h - strenc.c - mono-uri.c - mono-poll.c - mono-path.c - mono-os-semaphore.h - mono-coop-semaphore.h - mono-sigcontext.h - mono-stdlib.c - mono-property-hash.h - mono-property-hash.c - mono-value-hash.h - mono-value-hash.c - freebsd-elf_common.h - freebsd-elf32.h - freebsd-elf64.h - freebsd-dwarf.h - dtrace.h - gc_wrapper.h - mono-error.c - mono-error-internals.h - monobitset.h - mono-codeman.h - mono-counters.h - mono-digest.h - mono-error.h - mono-machine.h - mono-math.h - mono-membar.h - mono-path.h - mono-poll.h - mono-uri.h - mono-stdlib.h - valgrind.h - mach-support.h - memcheck.h - mono-context.c - mono-context.h - mono-stack-unwinding.h - hazard-pointer.c - hazard-pointer.h - lock-free-queue.c - lock-free-queue.h - lock-free-alloc.c - lock-free-alloc.h - lock-free-array-queue.c - lock-free-array-queue.h - mono-linked-list-set.c - mono-linked-list-set.h - mono-threads.c - mono-threads-state-machine.c - mono-threads-posix.c - mono-threads-posix-signals.c - mono-threads-mach.c - mono-threads-mach-helper.c - mono-threads-windows.c - mono-threads-linux.c - mono-threads-freebsd.c - mono-threads-netbsd.c - mono-threads-openbsd.c - mono-threads-android.c - mono-threads-haiku.c - mono-threads-aix.c - mono-threads-wasm.c - mono-threads.h - mono-threads-debug.h - mono-threads-api.h - mono-threads-coop.c - mono-threads-coop.h - mono-utility-thread.c - mono-utility-thread.h - mono-tls.h - mono-tls.c - mono-utils-debug.c - mono-utils-debug.h - linux_magic.h - mono-memory-model.h - atomic.h - atomic.c - mono-hwcap.h - mono-hwcap.c - mono-hwcap-vars.h - bsearch.h - bsearch.c - mono-signal-handler.h - mono-conc-hashtable.h - mono-conc-hashtable.c - json.h - json.c - networking.c - networking-posix.c - networking-fallback.c - networking-missing.c - networking-windows.c - networking.h - mono-rand.c - mono-rand-windows.c - mono-rand.h - memfuncs.c - memfuncs.h - parse.c - parse.h - checked-build.c - checked-build.h - os-event.h - refcount.h - w32api.h - unlocked.h - ward.h) - -if (TARGET_AMD64) -set (utils_arch_sources - mach-support-amd64.c) +set(utils_common_sources + mono-md5.c + mono-sha1.c + mono-logger.c + mono-logger-internals.h + mono-codeman.c + dlmalloc.h + dlmalloc.c + mono-counters.c + mono-compiler.h + mono-complex.h + mono-dl.c + mono-dl-windows.c + mono-dl-darwin.c + mono-dl-posix.c + mono-dl-wasm.c + mono-dl.h + mono-dl-windows-internals.h + mono-log-windows.c + mono-log-common.c + mono-log-posix.c + mono-log-android.c + mono-log-darwin.c + mono-log-flight-recorder.c + mono-merp.c + mono-merp.h + mono-state.h + mono-state.c + mono-internal-hash.c + mono-internal-hash.h + mono-io-portability.c + mono-io-portability.h + monobitset.c + mono-filemap.c + mono-math-c.c + mono-mmap.c + mono-mmap-windows.c + mono-mmap.h + mono-mmap-internals.h + mono-mmap-windows-internals.h + mono-os-mutex.h + mono-os-mutex.c + mono-flight-recorder.h + mono-flight-recorder.c + mono-os-wait.h + mono-coop-mutex.h + mono-once.h + mono-lazy-init.h + mono-networkinterfaces.c + mono-networkinterfaces.h + mono-proclib.c + mono-proclib-windows.c + mono-proclib.h + mono-proclib-windows-internals.h + mono-publib.c + mono-jemalloc.c + mono-string.h + mono-time.c + mono-time.h + strenc.h + strenc.c + mono-uri.c + mono-poll.c + mono-path.c + mono-os-semaphore.h + mono-coop-semaphore.h + mono-sigcontext.h + mono-stdlib.c + mono-property-hash.h + mono-property-hash.c + mono-value-hash.h + mono-value-hash.c + freebsd-elf_common.h + freebsd-elf32.h + freebsd-elf64.h + freebsd-dwarf.h + dtrace.h + gc_wrapper.h + mono-error.c + mono-error-internals.h + monobitset.h + mono-codeman.h + mono-counters.h + mono-digest.h + mono-error.h + mono-machine.h + mono-math.h + mono-membar.h + mono-path.h + mono-poll.h + mono-uri.h + mono-stdlib.h + valgrind.h + mach-support.h + memcheck.h + mono-context.c + mono-context.h + mono-stack-unwinding.h + hazard-pointer.c + hazard-pointer.h + lock-free-queue.c + lock-free-queue.h + lock-free-alloc.c + lock-free-alloc.h + lock-free-array-queue.c + lock-free-array-queue.h + lifo-semaphore.c + lifo-semaphore.h + mono-linked-list-set.c + mono-linked-list-set.h + mono-threads.c + mono-threads-state-machine.c + mono-threads-posix.c + mono-threads-posix-signals.c + mono-threads-mach.c + mono-threads-mach-helper.c + mono-threads-windows.c + mono-threads-linux.c + mono-threads-freebsd.c + mono-threads-netbsd.c + mono-threads-openbsd.c + mono-threads-android.c + mono-threads-haiku.c + mono-threads-aix.c + mono-threads-wasm.c + mono-threads-sunos.c + mono-threads.h + mono-threads-debug.h + mono-threads-api.h + mono-threads-coop.c + mono-threads-coop.h + mono-utility-thread.c + mono-utility-thread.h + mono-tls.h + mono-tls.c + mono-utils-debug.c + mono-utils-debug.h + linux_magic.h + mono-memory-model.h + atomic.h + atomic.c + mono-hwcap.h + mono-hwcap.c + mono-hwcap-vars.h + bsearch.h + bsearch.c + mono-signal-handler.h + mono-signal-handler.c + mono-conc-hashtable.h + mono-conc-hashtable.c + json.h + json.c + networking.c + networking-posix.c + networking-fallback.c + networking-missing.c + networking-windows.c + networking.h + mono-rand.c + mono-rand-windows.c + mono-rand.h + memfuncs.c + memfuncs.h + parse.c + parse.h + checked-build.c + checked-build.h + os-event.h + refcount.h + w32api.h + unlocked.h + ward.h) + +if(MONO_CROSS_COMPILE) +set(utils_arch_sources mach-support-unknown.c) +elseif(HOST_AMD64) +set(utils_arch_sources + mach-support-amd64.c) +elseif(HOST_X86) +set(utils_arch_sources + mach-support-x86.c) +elseif(HOST_ARM64) +set(utils_arch_sources + mach-support-arm64.c) +elseif(HOST_ARM) +set(utils_arch_sources + mach-support-arm.c) else() -#message (FATAL_ERROR "") +#message(FATAL_ERROR "") endif() -if (CROSS_COMPILE) -set (utils_arch_sources "${utils_arch_sources};mono-hwcap-cross.c") -elseif (TARGET_AMD64) -set (utils_arch_sources "${utils_arch_sources};mono-hwcap-x86.c") +if(MONO_CROSS_COMPILE) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-cross.c") +elseif(TARGET_AMD64) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-x86.c") +elseif(TARGET_X86) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-x86.c") +elseif(TARGET_ARM64) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-arm64.c") +elseif(TARGET_ARM) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-arm.c") +elseif(TARGET_WASM) +set(utils_arch_sources "${utils_arch_sources};mono-hwcap-wasm.c;mono-mmap-wasm.c") else() - message (FATAL_ERROR "") + message(FATAL_ERROR "") endif() -addprefix (utils_sources ../utils/ "${utils_platform_sources};${utils_arch_sources};${utils_common_sources}") +addprefix(utils_sources ../utils/ "${utils_platform_sources};${utils_arch_sources};${utils_common_sources}") + +set(utils_public_headers_base + mono-logger.h + mono-error.h + mono-forward.h + mono-publib.h + mono-jemalloc.h + mono-dl-fallback.h + mono-private-unstable.h + mono-counters.h) +addprefix(utils_public_headers ../utils "${utils_public_headers_base}") # # METADATA # -set (metadata_win32_sources - console-win32.c - console-win32-internals.h - cominterop-win32-internals.h - w32file-win32.c - w32file-win32-internals.h - icall-windows.c - icall-windows-internals.h - marshal-windows.c - marshal-windows-internals.h - mono-security-windows.c - mono-security-windows-internals.h - w32mutex-win32.c - w32semaphore-win32.c - w32event-win32.c - w32process-win32.c - w32process-win32-internals.h - w32socket-win32.c - w32error-win32.c - w32subset.h) - -set (metadata_unix_sources - console-unix.c - w32mutex-unix.c - w32semaphore-unix.c - w32event-unix.c - w32process-unix.c - w32process-unix-internals.h - w32process-unix-osx.c - w32process-unix-bsd.c - w32process-unix-haiku.c - w32process-unix-default.c - w32socket-unix.c - w32file-unix.c - w32file-unix-glob.c - w32file-unix-glob.h - w32error-unix.c) - -if (HOST_WIN32) -set (metadata_platform_sources ${metadata_win32_sources}) +# +# This library contains the icall tables if the runtime was configured with DISABLE_ICALL_TABLES +# +if(DISABLE_ICALL_TABLES) +add_library(mono-icall-table STATIC "../metadata/icall-table.c") +install(TARGETS mono-icall-table LIBRARY) +else() +set(icall_table_sources "icall-table.c") +endif() + +# +# This library contains runtime IL code generation +# +set(ilgen_base_sources + method-builder-ilgen.c + method-builder-ilgen.h + method-builder-ilgen-internals.h + marshal-ilgen.c + marshal-ilgen.h + sgen-mono-ilgen.c + sgen-mono-ilgen.h) + +if(NOT ENABLE_ILGEN) +addprefix(mono_ilgen_sources ../metadata "${ilgen_base_sources}") +set_source_files_properties(${mono_ilgen_sources} PROPERTIES COMPILE_DEFINITIONS "HAVE_SGEN_GC") +add_library(mono-ilgen STATIC "${mono_ilgen_sources}") +install(TARGETS mono-ilgen LIBRARY) +else() +set(ilgen_sources ${ilgen_base_sources}) +endif() + +set(metadata_win32_sources + console-win32.c + console-win32-internals.h + cominterop-win32-internals.h + w32file-win32.c + w32file-win32-internals.h + icall-windows.c + icall-windows-internals.h + marshal-windows.c + marshal-windows-internals.h + mono-security-windows.c + mono-security-windows-internals.h + w32mutex-win32.c + w32semaphore-win32.c + w32event-win32.c + w32process-win32.c + w32process-win32-internals.h + w32socket-win32.c + w32error-win32.c + w32subset.h) + +set(metadata_unix_sources + console-unix.c + w32mutex-unix.c + w32semaphore-unix.c + w32event-unix.c + w32process-unix.c + w32process-unix-internals.h + w32process-unix-osx.c + w32process-unix-bsd.c + w32process-unix-haiku.c + w32process-unix-default.c + w32socket-unix.c + w32file-unix.c + w32file-unix-glob.c + w32file-unix-glob.h + w32error-unix.c) + +if(HOST_WIN32) +set(metadata_platform_sources ${metadata_win32_sources}) else() -set (metadata_platform_sources ${metadata_unix_sources}) +set(metadata_platform_sources ${metadata_unix_sources}) endif() set(metadata_common_sources - appdomain.c - domain.c - appdomain-icalls.h - assembly.c - assembly-internals.h - attach.h - attach.c - cil-coff.h - class.c - class-getters.h - class-init.h - class-init.c - class-internals.h - class-inlines.h - class-private-definition.h - class-accessors.c - cominterop.c - cominterop.h - console-io.h - coree.c - coree.h - coree-internals.h - culture-info.h - culture-info-tables.h - debug-helpers.c - debug-mono-symfile.h - debug-mono-symfile.c - debug-mono-ppdb.h - debug-mono-ppdb.c - domain-internals.h - environment.c - environment.h - exception.c - exception.h - exception-internals.h - w32file.c - w32file.h - w32file-internals.h - filewatcher.c - filewatcher.h - gc-internals.h - icall.c - icall-internals.h - icall-def.h - icall-table.h - image.c - image-internals.h - jit-info.c - loader.c - locales.c - locales.h - lock-tracer.c - lock-tracer.h - marshal.c - marshal.h - marshal-internals.h - mempool.c - mempool.h - mempool-internals.h - metadata.c - metadata-verify.c - metadata-internals.h - method-builder.h - method-builder-internals.h - method-builder.c - mono-basic-block.c - mono-basic-block.h - mono-config.c - mono-debug.h - mono-debug.c - debug-internals.h - mono-endian.c - mono-endian.h - mono-hash.c - mono-hash.h - mono-conc-hash.c - mono-conc-hash.h - mono-mlist.c - mono-mlist.h - mono-perfcounters.c - mono-perfcounters.h - mono-perfcounters-def.h - mono-ptr-array.h - mono-route.c - monitor.h - normalization-tables.h - number-formatter.h - number-ms.h - object.c - object-forward.h - object-internals.h - opcodes.c - property-bag.h - property-bag.c - w32socket.c - w32socket.h - w32socket-internals.h - w32process.c - w32process.h - w32process-internals.h - profiler.c - profiler-private.h - rand.h - rand.c - remoting.h - remoting.c - runtime.c - mono-security.c - security.h - security-core-clr.c - security-core-clr.h - security-manager.c - security-manager.h - string-icalls.c - string-icalls.h - sysmath.c - tabledefs.h - threads.c - threads-types.h - threadpool.c - threadpool.h - threadpool-worker.h - threadpool-io.c - threadpool-io.h - verify.c - verify-internals.h - wrapper-types.h - dynamic-image-internals.h - dynamic-stream.c - dynamic-stream-internals.h - reflection-cache.h - custom-attrs-internals.h - sre-internals.h - reflection-internals.h - file-mmap-posix.c - file-mmap-windows.c - file-mmap.h - object-offsets.h - abi-details.h - class-abi-details.h - metadata-cross-helpers.c - seq-points-data.h - seq-points-data.c - handle.c - handle.h - w32mutex.h - w32semaphore.h - w32event.h - w32handle-namespace.h - w32handle-namespace.c - w32handle.h - w32handle.c - w32error.h - reflection.c - dynamic-image.c - sre.c - sre-encode.c - sre-save.c - custom-attrs.c - fdhandle.h - fdhandle.c - callspec.h - callspec.c - assembly-load-context.c - icall-eventpipe.c - native-library.h - native-library.c - native-library-qcall.c - qcalllist.h - loaded-images-internals.h - loaded-images.c - loaded-images-netcore.c - abi-details.h - abi.c - mono-config-dirs.h - mono-config-dirs.c - icall-table.h - icall-table.c - method-builder-ilgen.h - method-builder-ilgen.c - marshal-ilgen.h - marshal-ilgen.c) - -set (metadata_gc_dependent_sources + appdomain.c + domain.c + appdomain-icalls.h + assembly.c + assembly-internals.h + attach.h + attach.c + cil-coff.h + class.c + class-getters.h + class-init.h + class-init-internals.h + class-init.c + class-internals.h + class-inlines.h + class-private-definition.h + class-accessors.c + class-setup-vtable.c + cominterop.c + cominterop.h + console-io.h + coree.c + coree.h + coree-internals.h + debug-helpers.c + debug-mono-symfile.h + debug-mono-symfile.c + debug-mono-ppdb.h + debug-mono-ppdb.c + domain-internals.h + environment.c + environment.h + exception.c + exception.h + exception-internals.h + w32file.c + w32file.h + w32file-internals.h + filewatcher.c + filewatcher.h + gc-internals.h + icall.c + icall-internals.h + icall-def.h + icall-table.h + icall-eventpipe.c + image.c + image-internals.h + jit-info.c + loader.c + lock-tracer.c + lock-tracer.h + marshal.c + marshal.h + marshal-internals.h + mempool.c + mempool.h + mempool-internals.h + metadata.c + metadata-verify.c + metadata-internals.h + method-builder.h + method-builder-internals.h + method-builder.c + mono-basic-block.c + mono-basic-block.h + mono-config.c + mono-debug.h + mono-debug.c + debug-internals.h + mono-endian.c + mono-endian.h + mono-hash.c + mono-hash.h + mono-conc-hash.c + mono-conc-hash.h + mono-mlist.c + mono-mlist.h + mono-perfcounters.c + mono-perfcounters.h + mono-perfcounters-def.h + mono-ptr-array.h + mono-route.c + monitor.h + normalization-tables.h + number-formatter.h + number-ms.h + object.c + object-forward.h + object-internals.h + opcodes.c + property-bag.h + property-bag.c + w32socket.c + w32socket.h + w32socket-internals.h + w32process.c + w32process.h + w32process-internals.h + profiler.c + profiler-private.h + rand.h + rand.c + remoting.h + remoting.c + runtime.c + mono-security.c + security.h + security-core-clr.c + security-core-clr.h + security-manager.c + security-manager.h + string-icalls.c + string-icalls.h + sysmath.c + tabledefs.h + threads.c + threads-types.h + threadpool.c + threadpool.h + threadpool-worker.h + threadpool-io.c + threadpool-io.h + verify.c + verify-internals.h + wrapper-types.h + dynamic-image-internals.h + dynamic-stream.c + dynamic-stream-internals.h + reflection-cache.h + custom-attrs-internals.h + sre-internals.h + reflection-internals.h + file-mmap-posix.c + file-mmap-windows.c + file-mmap.h + object-offsets.h + abi-details.h + class-abi-details.h + metadata-cross-helpers.c + seq-points-data.h + seq-points-data.c + handle.c + handle.h + w32mutex.h + w32semaphore.h + w32event.h + w32handle-namespace.h + w32handle-namespace.c + w32handle.h + w32handle.c + w32error.h + reflection.c + dynamic-image.c + sre.c + sre-encode.c + sre-save.c + custom-attrs.c + fdhandle.h + fdhandle.c + callspec.h + callspec.c + assembly-load-context.c + icall-eventpipe.c + native-library.h + native-library.c + native-library-qcall.c + qcall-def.h + loaded-images-internals.h + loaded-images.c + loaded-images-netcore.c + abi-details.h + abi.c + mono-config-dirs.h + mono-config-dirs.c + icall-table.h + ${icall_table_sources}) + +set(metadata_gc_dependent_sources gc-stats.c gc.c monitor.c) -set (metadata_sgen_sources - sgen-bridge.c - sgen-bridge.h - sgen-bridge-internals.h - sgen-old-bridge.c - sgen-new-bridge.c - sgen-tarjan-bridge.c - sgen-toggleref.c - sgen-toggleref.h - sgen-stw.c - sgen-mono.c - sgen-mono.h - sgen-client-mono.h - sgen-mono-ilgen.h - sgen-mono-ilgen.c) - -addprefix (metadata_gc_dependent_real_sources ../metadata "${metadata_gc_dependent_sources}") -addprefix (metadata_sgen_real_sources ../metadata "${metadata_sgen_sources}") -set_source_files_properties (${metadata_sgen_real_sources};${metadata_gc_dependent_real_sources} PROPERTIES COMPILE_DEFINITIONS "HAVE_SGEN_GC") +set(metadata_sgen_sources + sgen-bridge.c + sgen-bridge.h + sgen-bridge-internals.h + sgen-old-bridge.c + sgen-new-bridge.c + sgen-tarjan-bridge.c + sgen-toggleref.c + sgen-toggleref.h + sgen-stw.c + sgen-mono.c + sgen-mono.h + sgen-client-mono.h) + +addprefix(metadata_gc_dependent_real_sources ../metadata "${metadata_gc_dependent_sources}") +addprefix(metadata_sgen_real_sources ../metadata "${metadata_sgen_sources}") +addprefix(ilgen_real_sources ../metadata "${ilgen_sources}") +set_source_files_properties(${metadata_sgen_real_sources};${metadata_gc_dependent_real_sources};${ilgen_real_sources} PROPERTIES COMPILE_DEFINITIONS "HAVE_SGEN_GC") # # We localize the usage of MONO_BINDIR etc. to just one source file, thus enabling # ccache to work even if the value of these defines change. # -set (bindir "${CMAKE_INSTALL_FULL_BINDIR}") -set (assembliesdir "${CMAKE_INSTALL_FULL_LIBDIR}") -set (confdir "${CMAKE_INSTALL_FULL_SYSCONFDIR}") -set (reloc_libdir "${CMAKE_INSTALL_LIBDIR}") +set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") +set(assembliesdir "${CMAKE_INSTALL_FULL_LIBDIR}") +set(confdir "${CMAKE_INSTALL_FULL_SYSCONFDIR}") +set(reloc_libdir "${CMAKE_INSTALL_LIBDIR}") -set_source_files_properties (../metadata/mono-config-dirs.c PROPERTIES COMPILE_DEFINITIONS "MONO_BINDIR=\"${bindir}\";MONO_ASSEMBLIES=\"${assembliesdir}\";MONO_CFG_DIR=\"${confdir}\";MONO_RELOC_LIBDIR=\"../${reloc_libdir}\"") +set_source_files_properties(../metadata/mono-config-dirs.c PROPERTIES COMPILE_DEFINITIONS "MONO_BINDIR=\"${bindir}\";MONO_ASSEMBLIES=\"${assembliesdir}\";MONO_CFG_DIR=\"${confdir}\";MONO_RELOC_LIBDIR=\"../${reloc_libdir}\"") -addprefix (metadata_sources ../metadata/ "${metadata_platform_sources};${metadata_common_sources};${metadata_gc_dependent_sources};${metadata_sgen_sources}") +addprefix(metadata_sources ../metadata/ "${metadata_platform_sources};${metadata_common_sources};${metadata_gc_dependent_sources};${metadata_sgen_sources};${ilgen_real_sources}") + +set(metadata_public_headers_base + appdomain.h + assembly.h + attrdefs.h + blob.h + class.h + debug-helpers.h + debug-mono-symfile.h + environment.h + exception.h + image.h + loader.h + metadata.h + mono-config.h + mono-debug.h + mono-gc.h + mono-private-unstable.h + object.h + object-forward.h + opcodes.h + profiler.h + profiler-events.h + reflection.h + row-indexes.h + sgen-bridge.h + threads.h + tokentype.h + verify.h) +addprefix(metadata_public_headers ../metadata/ "${metadata_public_headers_base}") # # SGEN # set(sgen_sources_base - gc-internal-agnostic.h - sgen-alloc.c - sgen-archdep.h - sgen-cardtable.c - sgen-cardtable.h - sgen-client.h - sgen-conf.h - sgen-copy-object.h - sgen-debug.c - sgen-descriptor.c - sgen-descriptor.h - sgen-fin-weak-hash.c - sgen-gc.c - sgen-gc.h - sgen-gchandles.c - sgen-gray.c - sgen-gray.h - sgen-hash-table.c - sgen-hash-table.h - sgen-internal.c - sgen-layout-stats.c - sgen-layout-stats.h - sgen-los.c - sgen-major-copy-object.h - sgen-marksweep-drain-gray-stack.h - sgen-marksweep.c - sgen-memory-governor.c - sgen-memory-governor.h - sgen-minor-copy-object.h - sgen-minor-scan-object.h - sgen-nursery-allocator.c - sgen-pinning-stats.c - sgen-pinning.c - sgen-pinning.h - sgen-pointer-queue.c - sgen-pointer-queue.h - sgen-array-list.h - sgen-array-list.c - sgen-protocol-def.h - sgen-protocol.c - sgen-protocol.h - sgen-qsort.c - sgen-qsort.h - sgen-scan-object.h - sgen-simple-nursery.c - sgen-split-nursery.c - sgen-tagged-pointer.h - sgen-thread-pool.c - sgen-thread-pool.h - sgen-workers.c - sgen-workers.h) - -addprefix (sgen_sources ../sgen/ "${sgen_sources_base}") -set_source_files_properties (${sgen_sources} PROPERTIES COMPILE_DEFINITIONS "HAVE_SGEN_GC") + gc-internal-agnostic.h + sgen-alloc.c + sgen-archdep.h + sgen-cardtable.c + sgen-cardtable.h + sgen-client.h + sgen-conf.h + sgen-copy-object.h + sgen-debug.c + sgen-descriptor.c + sgen-descriptor.h + sgen-fin-weak-hash.c + sgen-gc.c + sgen-gc.h + sgen-gchandles.c + sgen-gray.c + sgen-gray.h + sgen-hash-table.c + sgen-hash-table.h + sgen-internal.c + sgen-layout-stats.c + sgen-layout-stats.h + sgen-los.c + sgen-major-copy-object.h + sgen-marksweep-drain-gray-stack.h + sgen-marksweep.c + sgen-memory-governor.c + sgen-memory-governor.h + sgen-minor-copy-object.h + sgen-minor-scan-object.h + sgen-nursery-allocator.c + sgen-pinning-stats.c + sgen-pinning.c + sgen-pinning.h + sgen-pointer-queue.c + sgen-pointer-queue.h + sgen-array-list.h + sgen-array-list.c + sgen-protocol-def.h + sgen-protocol.c + sgen-protocol.h + sgen-qsort.c + sgen-qsort.h + sgen-scan-object.h + sgen-simple-nursery.c + sgen-split-nursery.c + sgen-tagged-pointer.h + sgen-thread-pool.c + sgen-thread-pool.h + sgen-workers.c + sgen-workers.h) + +addprefix(sgen_sources ../sgen/ "${sgen_sources_base}") +set_source_files_properties(${sgen_sources} PROPERTIES COMPILE_DEFINITIONS "HAVE_SGEN_GC") + +# EVENTPIPE + +set(eventpipe_sources_base + ep.c + ep.h + ep-block.c + ep-block.h + ep-buffer.c + ep-buffer.h + ep-buffer-manager.c + ep-buffer-manager.h + ep-config.c + ep-config.h + ep-config-internals.h + ep-event.c + ep-event.h + ep-event-instance.h + ep-event-instance.c + ep-event-payload.c + ep-event-payload.h + ep-event-source.c + ep-event-source.h + ep-file.c + ep-file.h + ep-getter-setter.h + ep-metadata-generator.c + ep-metadata-generator.h + ep-provider.c + ep-provider.h + ep-provider-internals.h + ep-rt.h + ep-rt-config.h + ep-rt-config-mono.h + ep-rt-mono.c + ep-rt-mono.h + ep-rt-types.h + ep-rt-types-mono.h + ep-thread.c + ep-thread.h + ep-types.h + ep-session.c + ep-session.h + ep-session-provider.c + ep-stack-contents.c + ep-stack-contents.h + ep-stream.c + ep-stream.h) +if(ENABLE_PERFTRACING) + addprefix(eventpipe_sources ../eventpipe "${eventpipe_sources_base}") +endif() + +# ICU +if(HAVE_SYS_ICU) +if(STATIC_ICU) +set(pal_icushim_sources_base + pal_icushim_static.c) +else() +set(pal_icushim_sources_base + pal_icushim.c) +endif() + +set(icu_shim_sources_base + pal_calendarData.c + pal_casing.c + pal_collation.c + pal_idna.c + pal_locale.c + pal_localeNumberData.c + pal_localeStringData.c + pal_normalization.c + pal_timeZoneInfo.c + entrypoints.c + ${pal_icushim_sources_base}) +addprefix(icu_shim_sources "${ICU_SHIM_PATH}" "${icu_shim_sources_base}") +set_source_files_properties(${icu_shim_sources} PROPERTIES COMPILE_DEFINITIONS OSX_ICU_LIBRARY_PATH="${OSX_ICU_LIBRARY_PATH}") +set_source_files_properties(${icu_shim_sources} PROPERTIES COMPILE_FLAGS "-I${ICU_INCLUDEDIR} -I${CMAKE_SOURCE_DIR}/../libraries/Native/Unix/System.Globalization.Native/ -I${CMAKE_SOURCE_DIR}/../libraries/Native/Unix/Common/ ${ICU_FLAGS}") +set(ICU_LIBS "icucore") +set(ICU_LDFLAGS "-L${ICU_LIBDIR}") +endif() # # MINI # -set (mini_common_sources - mini.c - mini-runtime.c - seq-points.c - seq-points.h - ir-emit.h - method-to-ir.c - cfgdump.h - cfgdump.c - calls.c - decompose.c - mini.h - version.h - optflags-def.h - jit-icalls.h - jit-icalls.c - trace.c - trace.h - patch-info.h - mini-ops.h - mini-arch.h - dominators.c - cfold.c - regalloc.h - helpers.c - liveness.c - ssa.c - abcremoval.c - abcremoval.h - local-propagation.c - driver.c - debug-mini.c - linear-scan.c - aot-compiler.h - aot-compiler.c - aot-runtime.c - aot-runtime-wasm.c - wasm_m2n_invoke.g.h - graph.c - mini-codegen.c - mini-exceptions.c - mini-trampolines.c - branch-opts.c - mini-generic-sharing.c - simd-methods.h - tasklets.c - tasklets.h - simd-intrinsics.c - mini-native-types.c - mini-unwind.h - unwind.c - image-writer.h - image-writer.c - dwarfwriter.h - dwarfwriter.c - mini-gc.h - mini-gc.c - xdebug.c - mini-llvm.h - mini-llvm-cpp.h - llvm-jit.h - alias-analysis.c - mini-cross-helpers.c - arch-stubs.c - llvm-runtime.h - type-checking.c - lldb.h - lldb.c - memory-access.c - intrinsics.c - mini-profiler.c - interp-stubs.c - aot-runtime.h - ee.h - mini-runtime.h - llvmonly-runtime.h - llvmonly-runtime.c - simd-intrinsics-netcore.c - monovm.h - monovm.c) - -set (debugger_sources +set(mini_common_sources + mini.c + mini-runtime.c + seq-points.c + seq-points.h + ir-emit.h + method-to-ir.c + cfgdump.h + cfgdump.c + calls.c + decompose.c + mini.h + version.h + optflags-def.h + jit-icalls.h + jit-icalls.c + trace.c + trace.h + patch-info.h + mini-ops.h + mini-arch.h + dominators.c + cfold.c + regalloc.h + helpers.c + liveness.c + ssa.c + abcremoval.c + abcremoval.h + local-propagation.c + driver.c + debug-mini.c + linear-scan.c + aot-compiler.h + aot-compiler.c + aot-runtime.c + graph.c + mini-codegen.c + mini-exceptions.c + mini-trampolines.c + branch-opts.c + mini-generic-sharing.c + simd-methods.h + tasklets.c + tasklets.h + simd-intrinsics.c + mini-native-types.c + mini-unwind.h + unwind.c + image-writer.h + image-writer.c + dwarfwriter.h + dwarfwriter.c + mini-gc.h + mini-gc.c + xdebug.c + mini-llvm.h + mini-llvm-cpp.h + llvm-jit.h + alias-analysis.c + mini-cross-helpers.c + arch-stubs.c + llvm-runtime.h + llvm-intrinsics.h + type-checking.c + lldb.h + lldb.c + memory-access.c + intrinsics.c + mini-profiler.c + interp-stubs.c + aot-runtime.h + ee.h + mini-runtime.h + llvmonly-runtime.h + llvmonly-runtime.c + simd-intrinsics-netcore.c + monovm.h + monovm.c) + +set(debugger_sources debugger-engine.h debugger-engine.c debugger-agent.h @@ -692,94 +862,205 @@ set (debugger_sources debugger-state-machine.h debugger-state-machine.c) -set (amd64_sources - mini-amd64.c - mini-amd64.h - exceptions-amd64.c - tramp-amd64.c - mini-amd64-gsharedvt.c - mini-amd64-gsharedvt.h - tramp-amd64-gsharedvt.c - cpu-amd64.h) - -if (TARGET_AMD64) -set (arch_sources ${amd64_sources}) +set(amd64_sources + mini-amd64.c + mini-amd64.h + exceptions-amd64.c + tramp-amd64.c + mini-amd64-gsharedvt.c + mini-amd64-gsharedvt.h + tramp-amd64-gsharedvt.c + cpu-amd64.h) + +set(x86_sources + mini-x86.c + mini-x86.h + exceptions-x86.c + tramp-x86.c + mini-x86-gsharedvt.c + tramp-x86-gsharedvt.c + cpu-x86.h) + +set(arm64_sources + mini-arm64.c + mini-arm64.h + exceptions-arm64.c + tramp-arm64.c + mini-arm64-gsharedvt.c + mini-arm64-gsharedvt.h + tramp-arm64-gsharedvt.c + cpu-arm64.h) + +set(arm_sources + mini-arm.c + mini-arm.h + exceptions-arm.c + tramp-arm.c + mini-arm-gsharedvt.c + mini-arm-gsharedvt.h + tramp-arm-gsharedvt.c + cpu-arm.h) + +set(wasm_sources + mini-wasm.c + tramp-wasm.c + exceptions-wasm.c + aot-runtime-wasm.c + mini-wasm-debugger.c + wasm_m2n_invoke.g.h) + +if(TARGET_AMD64) +set(arch_sources ${amd64_sources}) +elseif(TARGET_X86) +set(arch_sources ${x86_sources}) +elseif(TARGET_ARM64) +set(arch_sources ${arm64_sources}) +elseif(TARGET_ARM) +set(arch_sources ${arm_sources}) +elseif(TARGET_WASM) +set(arch_sources ${wasm_sources}) endif() -set (darwin_sources - mini-darwin.c) +set(darwin_sources + mini-darwin.c) -set (windows_sources - mini-windows.c - mini-windows.h - mini-windows-dllmain.c - mini-windows-dlldac.c) +set(windows_sources + mini-windows.c + mini-windows.h + mini-windows-dllmain.c + mini-windows-dlldac.c) -set (posix_sources - mini-posix.c) +set(posix_sources + mini-posix.c) -if (HOST_DARWIN) -set (os_sources "${darwin_sources};${posix_sources}") +if(HOST_DARWIN) +set(os_sources "${darwin_sources};${posix_sources}") +endif() + +set(interp_sources + interp/interp.h + interp/interp-internals.h + interp/interp.c + interp/interp-intrins.h + interp/interp-intrins.c + interp/mintops.h + interp/mintops.c + interp/transform.c) +set(interp_stub_sources + interp-stubs.c) + +if(NOT DISABLE_INTERPRETER) +set(mini_interp_sources ${interp_sources}) +else() +set(mini_interp_sources ${interp_stub_sources}) endif() -if (NOT DISABLE_INTERPRETER) -set (interp_sources - interp/interp.h - interp/interp-internals.h - interp/interp.c - interp/mintops.h - interp/mintops.c - interp/transform.c) -else () -set (interp_sources - interp-stubs.c) +if(ENABLE_INTERP_LIB) +add_library(mono-ee-interp STATIC "${interp_sources}") +install(TARGETS mono-ee-interp LIBRARY) endif() -if (ENABLE_LLVM) -set (llvm_sources - mini-llvm.c - mini-llvm-cpp.cpp - llvm-jit.cpp) +if(ENABLE_LLVM) +set(llvm_sources + mini-llvm.c + mini-llvm-cpp.cpp + llvm-jit.cpp) endif() -if (ENABLE_LLVM) -set (llvm_runtime_sources - llvm-runtime.cpp) -elseif (ENABLE_LLVM_RUNTIME) -set (llvm_runtime_sources - llvm-runtime.cpp) +if(ENABLE_LLVM) +set(llvm_runtime_sources + llvm-runtime.cpp) +elseif(ENABLE_LLVM_RUNTIME) +set(llvm_runtime_sources + llvm-runtime.cpp) endif() -set (mini_sources "${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h;main-core.c;${mini_common_sources};${arch_sources};${os_sources};${interp_sources};${llvm_sources};${debugger_sources};${llvm_runtime_sources}") +set(mini_public_headers_base + jit.h + mono-private-unstable.h) +addprefix(mini_public_headers ../mini "${mini_public_headers_base}") -add_library(monosgen-objects OBJECT "${eglib_sources};${metadata_sources};${utils_sources};${sgen_sources};${mini_sources}") -add_library (monosgen-static STATIC $) -add_library (monosgen SHARED $) +set(mini_sources "${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h;main-core.c;${mini_common_sources};${arch_sources};${os_sources};${mini_interp_sources};${llvm_sources};${debugger_sources};${llvm_runtime_sources}") -set_target_properties(monosgen PROPERTIES OUTPUT_NAME monosgen-2.0) -set_target_properties(monosgen PROPERTIES LIBRARY_OUTPUT_DIRECTORY .libs) +if(LLVM_LIBDIR) + link_directories(${LLVM_LIBDIR}) +endif() + +if(HOST_DARWIN) +set(OS_LIBS "-framework CoreFoundation" "-framework Foundation") +endif() +add_library(monosgen-objects OBJECT "${eglib_sources};${metadata_sources};${utils_sources};${sgen_sources};${icu_shim_sources};${eventpipe_sources};${mini_sources}") +add_library(monosgen-static STATIC $) +set_target_properties(monosgen-static PROPERTIES OUTPUT_NAME monosgen-2.0) +if(NOT DISABLE_LIBS) + install(TARGETS monosgen-static LIBRARY) + install(FILES ${metadata_public_headers} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mono-2.0/mono/metadata) + install(FILES ${utils_public_headers} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mono-2.0/mono/utils) + install(FILES ${mini_public_headers} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mono-2.0/mono/jit) +endif() +if(NOT DISABLE_SHARED_LIBS) + add_library(monosgen SHARED $) + set_target_properties(monosgen PROPERTIES OUTPUT_NAME monosgen-2.0) + set_target_properties(monosgen PROPERTIES LIBRARY_OUTPUT_DIRECTORY .libs) + target_link_libraries(monosgen ${OS_LIBS} "iconv" ${LLVM_LIBS} ${ICU_LIBS}) + install(TARGETS monosgen LIBRARY) +endif() + +# FIXME: Always rebuilds, creates non-deterministic builds # FIXME: Use the previous format -string(TIMESTAMP BUILD_DATE) +#string(TIMESTAMP BUILD_DATE) -add_custom_command ( +#add_custom_command( +# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h +# COMMAND echo "const char *build_date = \"${BUILD_DATE}\";" > ${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h +# VERBATIM +#) +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h - COMMAND echo "const char *build_date = \"${BUILD_DATE}\";" > ${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h + COMMAND echo "const char *build_date = \"\";" > ${CMAKE_CURRENT_BINARY_DIR}/buildver-sgen.h VERBATIM ) -add_custom_command ( +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.h COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen-version-h.sh ${CMAKE_CURRENT_SOURCE_DIR}/../.. VERBATIM ) -add_custom_command ( +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpu-amd64.h COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/genmdesc.py TARGET_AMD64 ${CMAKE_CURRENT_SOURCE_DIR} cpu-amd64.h amd64_desc ${CMAKE_CURRENT_SOURCE_DIR}/cpu-amd64.md VERBATIM ) -add_executable (mono-sgen "main-sgen.c") -target_link_libraries (mono-sgen monosgen-static "-framework CoreFoundation" "-framework Foundation" "iconv" ${LLVM_LIBS}) -target_link_libraries (monosgen "-framework CoreFoundation" "-framework Foundation" "iconv" ${LLVM_LIBS}) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpu-x86.h + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/genmdesc.py TARGET_X86 ${CMAKE_CURRENT_SOURCE_DIR} cpu-x86.h x86_desc ${CMAKE_CURRENT_SOURCE_DIR}/cpu-x86.md + VERBATIM +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpu-arm64.h + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/genmdesc.py TARGET_ARM64 ${CMAKE_CURRENT_SOURCE_DIR} cpu-arm64.h arm64_cpu_desc ${CMAKE_CURRENT_SOURCE_DIR}/cpu-arm64.md + VERBATIM +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpu-arm.h + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/genmdesc.py TARGET_ARM ${CMAKE_CURRENT_SOURCE_DIR} cpu-arm.h arm_cpu_desc ${CMAKE_CURRENT_SOURCE_DIR}/cpu-arm.md + VERBATIM +) + +if(NOT DISABLE_EXECUTABLES) +set(CMAKE_SKIP_RPATH 1) +add_executable(mono-sgen "main-sgen.c") +target_link_libraries(mono-sgen monosgen-static ${OS_LIBS} "iconv" ${LLVM_LIBS} ${ICU_LIBS}) +if(ICU_LDFLAGS) + set_target_properties(mono-sgen monosgen PROPERTIES LINK_FLAGS ${ICU_LDFLAGS}) +endif() +install(TARGETS mono-sgen RUNTIME) +endif() diff --git a/mono/mini/aot-compiler.c b/mono/mini/aot-compiler.c index 5e8d1d3ab734..5ca434fcf1be 100644 --- a/mono/mini/aot-compiler.c +++ b/mono/mini/aot-compiler.c @@ -3424,12 +3424,14 @@ get_shared_ginst_ref (MonoAotCompile *acfg, MonoGenericInst *ginst) guint32 offset = GPOINTER_TO_UINT (g_hash_table_lookup (acfg->ginst_blob_hash, ginst)); if (!offset) { guint8 *buf2, *p2; + int len; - buf2 = (guint8 *)g_malloc (1024); + len = 1024 + (ginst->type_argc * 32); + buf2 = (guint8 *)g_malloc (len); p2 = buf2; encode_ginst (acfg, ginst, p2, &p2); - g_assert (p2 - buf2 < 1024); + g_assert (p2 - buf2 < len); offset = add_to_blob (acfg, buf2, p2 - buf2); g_free (buf2); @@ -5191,6 +5193,17 @@ check_type_depth (MonoType *t, int depth) static void add_types_from_method_header (MonoAotCompile *acfg, MonoMethod *method); +static gboolean +inst_has_vtypes (MonoGenericInst *inst) +{ + for (int i = 0; i < inst->type_argc; ++i) { + MonoType *t = inst->type_argv [i]; + if (MONO_TYPE_ISSTRUCT (t)) + return TRUE; + } + return FALSE; +} + /* * add_generic_class: * @@ -5203,6 +5216,7 @@ add_generic_class_with_depth (MonoAotCompile *acfg, MonoClass *klass, int depth, MonoClassField *field; gpointer iter; gboolean use_gsharedvt = FALSE; + gboolean use_gsharedvt_for_array = FALSE; if (!acfg->ginst_hash) acfg->ginst_hash = g_hash_table_new (NULL, NULL); @@ -5246,6 +5260,18 @@ add_generic_class_with_depth (MonoAotCompile *acfg, MonoClass *klass, int depth, (!strcmp (m_class_get_name (klass), "Dictionary`2") || !strcmp (m_class_get_name (klass), "List`1") || !strcmp (m_class_get_name (klass), "ReadOnlyCollection`1"))) use_gsharedvt = TRUE; +#ifdef TARGET_WASM + /* + * Use gsharedvt for instances with vtype arguments. + * WASM only since other platforms depend on the + * previous behavior. + */ + if ((acfg->jit_opts & MONO_OPT_GSHAREDVT) && mono_class_is_ginst (klass) && mono_class_get_generic_class (klass)->context.class_inst && is_vt_inst (mono_class_get_generic_class (klass)->context.class_inst)) { + use_gsharedvt = TRUE; + use_gsharedvt_for_array = TRUE; + } +#endif + iter = NULL; while ((method = mono_class_get_methods (klass, &iter))) { if ((acfg->jit_opts & MONO_OPT_GSHAREDVT) && method->is_inflated && mono_method_get_context (method)->method_inst) { @@ -5254,7 +5280,7 @@ add_generic_class_with_depth (MonoAotCompile *acfg, MonoClass *klass, int depth, */ continue; } - + if (mono_method_is_generic_sharable_full (method, FALSE, FALSE, use_gsharedvt)) { /* Already added */ add_types_from_method_header (acfg, method); @@ -5335,7 +5361,7 @@ add_generic_class_with_depth (MonoAotCompile *acfg, MonoClass *klass, int depth, if (!strncmp (method->name, name_prefix, strlen (name_prefix))) { MonoMethod *m = mono_aot_get_array_helper_from_wrapper (method); - if (m->is_inflated && !mono_method_is_generic_sharable_full (m, FALSE, FALSE, FALSE)) + if (m->is_inflated && !mono_method_is_generic_sharable_full (m, FALSE, FALSE, use_gsharedvt_for_array)) add_extra_method_with_depth (acfg, m, depth); } } @@ -6605,6 +6631,7 @@ encode_patch (MonoAotCompile *acfg, MonoJumpInfo *patch_info, guint8 *buf, guint case MONO_PATCH_INFO_ICALL_ADDR_CALL: case MONO_PATCH_INFO_METHOD_RGCTX: case MONO_PATCH_INFO_METHOD_CODE_SLOT: + case MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE: encode_method_ref (acfg, patch_info->data.method, p, &p); break; case MONO_PATCH_INFO_AOT_JIT_INFO: @@ -8484,7 +8511,8 @@ can_encode_patch (MonoAotCompile *acfg, MonoJumpInfo *patch_info) case MONO_PATCH_INFO_METHOD: case MONO_PATCH_INFO_METHOD_FTNDESC: case MONO_PATCH_INFO_METHODCONST: - case MONO_PATCH_INFO_METHOD_CODE_SLOT: { + case MONO_PATCH_INFO_METHOD_CODE_SLOT: + case MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE: { MonoMethod *method = patch_info->data.method; return can_encode_method (acfg, method); diff --git a/mono/mini/aot-runtime.c b/mono/mini/aot-runtime.c index b42c76efba75..27d27add698c 100644 --- a/mono/mini/aot-runtime.c +++ b/mono/mini/aot-runtime.c @@ -3691,10 +3691,14 @@ mono_aot_find_jit_info (MonoDomain *domain, MonoImage *image, gpointer addr) code1 = (guint8 *)methods [pos]; if (pos + 1 == methods_len) { +#ifdef HOST_WASM + code2 = code1 + 1; +#else if (code1 >= amodule->jit_code_start && code1 < amodule->jit_code_end) code2 = amodule->jit_code_end; else code2 = amodule->llvm_code_end; +#endif } else { code2 = (guint8 *)methods [pos + 1]; } @@ -3707,6 +3711,11 @@ mono_aot_find_jit_info (MonoDomain *domain, MonoImage *image, gpointer addr) break; } +#ifdef HOST_WASM + if (addr != methods [pos]) + return NULL; +#endif + g_assert (addr >= methods [pos]); if (pos + 1 < methods_len) g_assert (addr < methods [pos + 1]); @@ -3729,6 +3738,10 @@ mono_aot_find_jit_info (MonoDomain *domain, MonoImage *image, gpointer addr) code = (guint8 *)amodule->methods [method_index]; ex_info = &amodule->blob [mono_aot_get_offset (amodule->ex_info_offsets, method_index)]; +#ifdef HOST_WASM + /* WASM methods have no length, can only look up the method address */ + code_len = 1; +#else if (pos == methods_len - 1) { if (code >= amodule->jit_code_start && code < amodule->jit_code_end) code_len = amodule->jit_code_end - code; @@ -3737,6 +3750,7 @@ mono_aot_find_jit_info (MonoDomain *domain, MonoImage *image, gpointer addr) } else { code_len = (guint8*)methods [pos + 1] - (guint8*)methods [pos]; } +#endif g_assert ((guint8*)code <= (guint8*)addr && (guint8*)addr < (guint8*)code + code_len); @@ -3854,7 +3868,8 @@ decode_patch (MonoAotModule *aot_module, MonoMemPool *mp, MonoJumpInfo *ji, guin case MONO_PATCH_INFO_ICALL_ADDR: case MONO_PATCH_INFO_ICALL_ADDR_CALL: case MONO_PATCH_INFO_METHOD_RGCTX: - case MONO_PATCH_INFO_METHOD_CODE_SLOT: { + case MONO_PATCH_INFO_METHOD_CODE_SLOT: + case MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE: { MethodRef ref; gboolean res; @@ -4518,6 +4533,20 @@ add_module_cb (gpointer key, gpointer value, gpointer user_data) g_ptr_array_add ((GPtrArray*)user_data, value); } +static gboolean +inst_is_private (MonoGenericInst *inst) +{ + for (int i = 0; i < inst->type_argc; ++i) { + MonoType *t = inst->type_argv [i]; + if ((t->type == MONO_TYPE_CLASS || t->type == MONO_TYPE_VALUETYPE)) { + int access_level = mono_class_get_flags (t->data.klass) & TYPE_ATTRIBUTE_VISIBILITY_MASK; + if (access_level == TYPE_ATTRIBUTE_NESTED_PRIVATE || access_level == TYPE_ATTRIBUTE_NOT_PUBLIC) + return TRUE; + } + } + return FALSE; +} + gboolean mono_aot_can_dedup (MonoMethod *method) { @@ -4543,9 +4572,16 @@ mono_aot_can_dedup (MonoMethod *method) if (method->is_inflated && !mono_method_is_generic_sharable_full (method, TRUE, FALSE, FALSE) && !mini_is_gsharedvt_signature (mono_method_signature_internal (method)) && - !mini_is_gsharedvt_klass (method->klass)) + !mini_is_gsharedvt_klass (method->klass)) { + MonoGenericContext *context = mono_method_get_context (method); + if (context->method_inst && mini_is_gsharedvt_inst (context->method_inst)) + return FALSE; + /* No point in dedup-ing private instances */ + if ((context->class_inst && inst_is_private (context->class_inst)) || + (context->method_inst && inst_is_private (context->method_inst))) + return FALSE; return TRUE; - + } return FALSE; #else gboolean not_normal_gshared = method->is_inflated && !mono_method_is_generic_sharable_full (method, TRUE, FALSE, FALSE); diff --git a/mono/mini/cpu-amd64.md b/mono/mini/cpu-amd64.md index 06abb8e7e0d7..e581a983944f 100644 --- a/mono/mini/cpu-amd64.md +++ b/mono/mini/cpu-amd64.md @@ -24,9 +24,9 @@ # # len:number describe the maximun length in bytes of the instruction # number is a positive integer. If the length is not specified -# it defaults to zero. But lengths are only checked if the given opcode -# is encountered during compilation. Some opcodes, like CONV_U4 are -# transformed into other opcodes in the brg files, so they do not show up +# it defaults to zero. But lengths are only checked if the given opcode +# is encountered during compilation. Some opcodes, like CONV_U4 are +# transformed into other opcodes in the brg files, so they do not show up # during code generation. # # cost:number describe how many cycles are needed to complete the instruction (unused) @@ -270,7 +270,7 @@ float_conv_to_u1: dest:i src1:f len:49 float_conv_to_i: dest:i src1:f len:49 float_conv_to_ovf_i: dest:a src1:f len:40 float_conv_to_ovd_u: dest:a src1:f len:40 -float_mul_ovf: +float_mul_ovf: float_ceq: dest:i src1:f src2:f len:35 float_cgt: dest:i src1:f src2:f len:35 float_cgt_un: dest:i src1:f src2:f len:48 @@ -711,11 +711,11 @@ unpack_highq: dest:x src1:x src2:x len:5 clob:1 unpack_highps: dest:x src1:x src2:x len:5 clob:1 unpack_highpd: dest:x src1:x src2:x len:5 clob:1 -packw: dest:x src1:x src2:x len:5 clob:1 -packd: dest:x src1:x src2:x len:5 clob:1 +packw: dest:x src1:x src2:x len:5 clob:1 +packd: dest:x src1:x src2:x len:5 clob:1 -packw_un: dest:x src1:x src2:x len:5 clob:1 -packd_un: dest:x src1:x src2:x len:6 clob:1 +packw_un: dest:x src1:x src2:x len:5 clob:1 +packd_un: dest:x src1:x src2:x len:6 clob:1 paddb_sat: dest:x src1:x src2:x len:5 clob:1 paddb_sat_un: dest:x src1:x src2:x len:5 clob:1 @@ -782,7 +782,7 @@ extract_i2: dest:i src1:x len:13 extract_u2: dest:i src1:x len:13 extract_i1: dest:i src1:x len:13 extract_u1: dest:i src1:x len:13 -extract_r8: dest:f src1:x len:5 +extract_r8: dest:f src1:x len:5 iconv_to_r4_raw: dest:f src1:i len:10 @@ -804,7 +804,7 @@ loadx_aligned_membase: dest:x src1:b len:7 storex_aligned_membase_reg: dest:b src1:x len:7 storex_nta_membase_reg: dest:b src1:x len:7 -fconv_to_r8_x: dest:x src1:f len:4 +fconv_to_r8_x: dest:x src1:f len:4 xconv_r8_to_i4: dest:y src1:x len:7 prefetch_membase: src1:b len:4 diff --git a/mono/mini/cpu-arm.md b/mono/mini/cpu-arm.md index f3b6641d3f53..a58d4bb0e29b 100644 --- a/mono/mini/cpu-arm.md +++ b/mono/mini/cpu-arm.md @@ -130,8 +130,8 @@ storei2_membase_imm: dest:b len:20 storei2_membase_reg: dest:b src1:i len:12 storei4_membase_imm: dest:b len:20 storei4_membase_reg: dest:b src1:i len:20 -storei8_membase_imm: dest:b -storei8_membase_reg: dest:b src1:i +storei8_membase_imm: dest:b +storei8_membase_reg: dest:b src1:i storer4_membase_reg: dest:b src1:f len:60 storer8_membase_reg: dest:b src1:f len:24 store_memindex: dest:b src1:i src2:i len:4 diff --git a/mono/mini/cpu-mips.md b/mono/mini/cpu-mips.md index cbe7788a6b0f..96920a0747b5 100644 --- a/mono/mini/cpu-mips.md +++ b/mono/mini/cpu-mips.md @@ -118,7 +118,7 @@ storei2_membase_imm: dest:b len:20 storei2_membase_reg: dest:b src1:i len:20 storei4_membase_imm: dest:b len:20 storei4_membase_reg: dest:b src1:i len:20 -storei8_membase_imm: dest:b +storei8_membase_imm: dest:b storei8_membase_reg: dest:b src1:i len:20 storer4_membase_reg: dest:b src1:f len:20 storer8_membase_reg: dest:b src1:f len:20 @@ -156,7 +156,7 @@ add_imm: dest:i src1:i len:12 sub_imm: dest:i src1:i len:12 mul_imm: dest:i src1:i len:20 # there is no actual support for division or reminder by immediate -# we simulate them, though (but we need to change the burg rules +# we simulate them, though (but we need to change the burg rules # to allocate a symbolic reg for src2) div_imm: dest:i src1:i src2:i len:20 div_un_imm: dest:i src1:i src2:i len:12 @@ -346,7 +346,7 @@ long_xor_imm: dest:i src1:i clob:1 len:4 lcompare: src1:i src2:i len:4 lcompare_imm: src1:i len:12 -long_conv_to_r_un: dest:f src1:i src2:i len:37 +long_conv_to_r_un: dest:f src1:i src2:i len:37 float_beq: len:16 float_bne_un: len:16 diff --git a/mono/mini/cpu-ppc.md b/mono/mini/cpu-ppc.md index cb31d18c908c..6ef33f288722 100644 --- a/mono/mini/cpu-ppc.md +++ b/mono/mini/cpu-ppc.md @@ -137,7 +137,7 @@ add_imm: dest:i src1:i len:4 sub_imm: dest:i src1:i len:4 mul_imm: dest:i src1:i len:4 # there is no actual support for division or reminder by immediate -# we simulate them, though (but we need to change the burg rules +# we simulate them, though (but we need to change the burg rules # to allocate a symbolic reg for src2) div_imm: dest:i src1:i src2:i len:20 div_un_imm: dest:i src1:i src2:i len:12 @@ -164,8 +164,8 @@ cond_exc_no: len:8 cond_exc_c: len:12 cond_exc_nc: len:8 long_conv_to_ovf_i: dest:i src1:i src2:i len:32 -long_mul_ovf: -long_conv_to_r_un: dest:f src1:i src2:i len:37 +long_mul_ovf: +long_conv_to_r_un: dest:f src1:i src2:i len:37 float_beq: len:8 float_bne_un: len:8 float_blt: len:8 diff --git a/mono/mini/cpu-ppc64.md b/mono/mini/cpu-ppc64.md index f0651f5f8d05..fc0e634baa46 100644 --- a/mono/mini/cpu-ppc64.md +++ b/mono/mini/cpu-ppc64.md @@ -141,7 +141,7 @@ add_imm: dest:i src1:i len:4 sub_imm: dest:i src1:i len:4 mul_imm: dest:i src1:i len:4 # there is no actual support for division or reminder by immediate -# we simulate them, though (but we need to change the burg rules +# we simulate them, though (but we need to change the burg rules # to allocate a symbolic reg for src2) div_imm: dest:i src1:i src2:i len:20 div_un_imm: dest:i src1:i src2:i len:12 diff --git a/mono/mini/cpu-s390x.md b/mono/mini/cpu-s390x.md index 34295c32bde9..5db404947639 100644 --- a/mono/mini/cpu-s390x.md +++ b/mono/mini/cpu-s390x.md @@ -229,7 +229,7 @@ storei2_membase_reg: dest:b src1:i len:26 storei4_membase_imm: dest:b len:46 storei4_membase_reg: dest:b src1:i len:26 storei8_membase_imm: dest:b len:46 -storei8_membase_reg: dest:b src1:i len:26 +storei8_membase_reg: dest:b src1:i len:26 storer4_membase_reg: dest:b src1:f len:28 storer8_membase_reg: dest:b src1:f len:24 sub_imm: dest:i src1:i len:18 @@ -332,8 +332,8 @@ long_div: dest:i src1:i src2:i len:12 long_div_un: dest:i src1:i src2:i len:16 long_mul: dest:i src1:i src2:i len:12 long_mul_imm: dest:i src1:i len:20 -long_mul_ovf: dest:i src1:i src2:i len:56 -long_mul_ovf_un: dest:i src1:i src2:i len:64 +long_mul_ovf: dest:i src1:i src2:i len:56 +long_mul_ovf_un: dest:i src1:i src2:i len:64 long_and: dest:i src1:i src2:i len:8 long_or: dest:i src1:i src2:i len:8 long_xor: dest:i src1:i src2:i len:8 @@ -368,7 +368,7 @@ long_conv_to_u2: dest:i src1:i len:24 long_conv_to_u4: dest:i src1:i len:4 long_conv_to_u8: dest:i src1:i len:4 long_conv_to_u: dest:i src1:i len:4 -long_conv_to_r_un: dest:f src1:i len:37 +long_conv_to_r_un: dest:f src1:i len:37 long_beq: len:8 long_bge_un: len:8 @@ -399,7 +399,7 @@ int_conv_to_i: dest:i src1:i len:4 int_conv_to_u1: dest:i src1:i len:10 int_conv_to_u2: dest:i src1:i len:16 int_conv_to_u4: dest:i src1:i len:4 -int_conv_to_r_un: dest:f src1:i len:37 +int_conv_to_r_un: dest:f src1:i len:37 cond_exc_ic: len:8 cond_exc_ieq: len:8 @@ -431,9 +431,9 @@ vcall2_membase: src1:b len:12 clob:c vcall2_reg: src1:i len:8 clob:c s390_int_add_ovf: len:32 dest:i src1:i src2:i -s390_int_add_ovf_un: len:32 dest:i src1:i src2:i +s390_int_add_ovf_un: len:32 dest:i src1:i src2:i s390_int_sub_ovf: len:32 dest:i src1:i src2:i -s390_int_sub_ovf_un: len:32 dest:i src1:i src2:i +s390_int_sub_ovf_un: len:32 dest:i src1:i src2:i s390_long_add_ovf: dest:i src1:i src2:i len:32 s390_long_add_ovf_un: dest:i src1:i src2:i len:32 diff --git a/mono/mini/cpu-sparc.md b/mono/mini/cpu-sparc.md index 341b11e5b348..403a73c380ee 100644 --- a/mono/mini/cpu-sparc.md +++ b/mono/mini/cpu-sparc.md @@ -263,8 +263,8 @@ long_shl: dest:i src1:i src2:i len:64 long_shr: dest:i src1:i src2:i len:64 long_shr_un: dest:i src1:i src2:i len:64 long_conv_to_ovf_i: dest:i src1:i src2:i len:48 -long_mul_ovf: -long_conv_to_r_un: dest:f src1:i src2:i len:64 +long_mul_ovf: +long_conv_to_r_un: dest:f src1:i src2:i len:64 long_shr_imm: dest:i src1:i len:64 long_shr_un_imm: dest:i src1:i len:64 long_shl_imm: dest:i src1:i len:64 diff --git a/mono/mini/cpu-x86.md b/mono/mini/cpu-x86.md index 85e3ea7cf4ea..a0f0fc14f15d 100644 --- a/mono/mini/cpu-x86.md +++ b/mono/mini/cpu-x86.md @@ -25,9 +25,9 @@ # # len:number describe the maximun length in bytes of the instruction # number is a positive integer. If the length is not specified -# it defaults to zero. But lengths are only checked if the given opcode -# is encountered during compilation. Some opcodes, like CONV_U4 are -# transformed into other opcodes in the brg files, so they do not show up +# it defaults to zero. But lengths are only checked if the given opcode +# is encountered during compilation. Some opcodes, like CONV_U4 are +# transformed into other opcodes in the brg files, so they do not show up # during code generation. # # cost:number describe how many cycles are needed to complete the instruction (unused) @@ -180,8 +180,8 @@ storei2_membase_imm: dest:b len:11 storei2_membase_reg: dest:b src1:i len:7 storei4_membase_imm: dest:b len:10 storei4_membase_reg: dest:b src1:i len:7 -storei8_membase_imm: dest:b -storei8_membase_reg: dest:b src1:i +storei8_membase_imm: dest:b +storei8_membase_reg: dest:b src1:i storer4_membase_reg: dest:b src1:f len:7 storer8_membase_reg: dest:b src1:f len:7 load_membase: dest:i src1:b len:7 @@ -257,7 +257,7 @@ float_conv_to_u1: dest:y src1:f len:39 float_conv_to_i: dest:i src1:f len:39 float_conv_to_ovf_i: dest:a src1:f len:30 float_conv_to_ovd_u: dest:a src1:f len:30 -float_mul_ovf: +float_mul_ovf: float_ceq: dest:y src1:f src2:f len:25 float_cgt: dest:y src1:f src2:f len:25 float_cgt_un: dest:y src1:f src2:f len:37 @@ -425,7 +425,7 @@ cmov_ile_un: dest:i src1:i src2:i len:16 clob:1 cmov_ilt_un: dest:i src1:i src2:i len:16 clob:1 long_conv_to_ovf_i4_2: dest:i src1:i src2:i len:30 -long_conv_to_r8_2: dest:f src1:i src2:i len:14 +long_conv_to_r8_2: dest:f src1:i src2:i len:14 long_conv_to_r4_2: dest:f src1:i src2:i len:14 long_conv_to_r_un_2: dest:f src1:i src2:i len:40 @@ -565,11 +565,11 @@ unpack_highq: dest:x src1:x src2:x len:4 clob:1 unpack_highps: dest:x src1:x src2:x len:3 clob:1 unpack_highpd: dest:x src1:x src2:x len:4 clob:1 -packw: dest:x src1:x src2:x len:4 clob:1 -packd: dest:x src1:x src2:x len:4 clob:1 +packw: dest:x src1:x src2:x len:4 clob:1 +packd: dest:x src1:x src2:x len:4 clob:1 -packw_un: dest:x src1:x src2:x len:4 clob:1 -packd_un: dest:x src1:x src2:x len:5 clob:1 +packw_un: dest:x src1:x src2:x len:4 clob:1 +packd_un: dest:x src1:x src2:x len:5 clob:1 paddb_sat: dest:x src1:x src2:x len:4 clob:1 paddb_sat_un: dest:x src1:x src2:x len:4 clob:1 @@ -634,7 +634,7 @@ extract_i2: dest:i src1:x len:10 extract_u2: dest:i src1:x len:10 extract_i1: dest:i src1:x len:10 extract_u1: dest:i src1:x len:10 -extract_r8: dest:f src1:x len:8 +extract_r8: dest:f src1:x len:8 insert_i2: dest:x src1:x src2:i len:5 clob:1 @@ -653,7 +653,7 @@ loadx_aligned_membase: dest:x src1:b len:7 storex_aligned_membase_reg: dest:b src1:x len:7 storex_nta_membase_reg: dest:b src1:x len:7 -fconv_to_r8_x: dest:x src1:f len:14 +fconv_to_r8_x: dest:x src1:f len:14 xconv_r8_to_i4: dest:y src1:x len:7 prefetch_membase: src1:b len:4 diff --git a/mono/mini/debugger-agent.c b/mono/mini/debugger-agent.c index 5b6d729a3390..780332672fc6 100644 --- a/mono/mini/debugger-agent.c +++ b/mono/mini/debugger-agent.c @@ -4027,6 +4027,9 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx DebuggerTlsData *tls; tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id); g_assert (tls); + // We are already processing a breakpoint event + if (tls->disable_breakpoints) + return; mono_stopwatch_stop (&tls->step_time); break; } diff --git a/mono/mini/helpers.c b/mono/mini/helpers.c index eddfe646d78f..15cbd0a2277c 100644 --- a/mono/mini/helpers.c +++ b/mono/mini/helpers.c @@ -129,10 +129,8 @@ mono_disassemble_code (MonoCompile *cfg, guint8 *code, int size, char *id) #ifdef HOST_WIN32 const char *tmp = g_get_tmp_dir (); #endif - char *objdump_args = g_getenv ("MONO_OBJDUMP_ARGS"); char *as_file; char *o_file; - char *cmd; int unused G_GNUC_UNUSED; #ifdef HOST_WIN32 @@ -260,9 +258,10 @@ mono_disassemble_code (MonoCompile *cfg, guint8 *code, int size, char *id) #endif #ifdef HAVE_SYSTEM - cmd = g_strdup_printf (ARCH_PREFIX AS_CMD " %s -o %s", as_file, o_file); + char *cmd = g_strdup_printf (ARCH_PREFIX AS_CMD " %s -o %s", as_file, o_file); unused = system (cmd); g_free (cmd); + char *objdump_args = g_getenv ("MONO_OBJDUMP_ARGS"); if (!objdump_args) objdump_args = g_strdup (""); diff --git a/mono/mini/interp/interp-internals.h b/mono/mini/interp/interp-internals.h index e3f3e04c8c9e..a08467100ee3 100644 --- a/mono/mini/interp/interp-internals.h +++ b/mono/mini/interp/interp-internals.h @@ -51,50 +51,10 @@ typedef gint64 mono_i; #define MINT_TYPE_I MINT_TYPE_I8 #endif - -/* - * GC SAFETY: - * - * The interpreter executes in gc unsafe (non-preempt) mode. On wasm, the C stack is - * scannable but the wasm stack is not, so to make the code GC safe, the following rules - * should be followed: - * - every objref handled by the code needs to either be stored volatile or stored - * into a volatile; volatile stores are stack packable, volatile values are not. - * Use either OBJREF or stackval->data.o. - * This will ensure the objects are pinned. A volatile local - * is on the stack and not in registers. Volatile stores ditto. - * - minimize the number of MonoObject* locals/arguments (or make them volatile). - * - * Volatile on a type/local forces all reads and writes to go to memory/stack, - * and each such local to have a unique address. - * - * Volatile absence on a type/local allows multiple locals to share storage, - * if their lifetimes do not overlap. This is called "stack packing". - * - * Volatile absence on a type/local allows the variable to live in - * both stack and register, for fast reads and "write through". - */ #ifdef TARGET_WASM - -#define WASM_VOLATILE volatile - -static inline MonoObject * WASM_VOLATILE * -mono_interp_objref (MonoObject **o) -{ - return o; -} - -#define OBJREF(x) (*mono_interp_objref (&x)) - -#else - -#define WASM_VOLATILE /* nothing */ - -#define OBJREF(x) x - +#define INTERP_NO_STACK_SCAN 1 #endif - /* * Value types are represented on the eval stack as pointers to the * actual storage. A value type cannot be larger than 16 MB. @@ -109,7 +69,12 @@ typedef struct { } pair; float f_r4; double f; - MonoObject * WASM_VOLATILE o; +#ifdef INTERP_NO_STACK_SCAN + /* Ensure objref is always flushed to interp stack */ + MonoObject * volatile o; +#else + MonoObject *o; +#endif /* native size integer and pointer types */ gpointer p; mono_u nati; diff --git a/mono/mini/interp/interp.c b/mono/mini/interp/interp.c index 53d20e1997c8..9cafe0736df5 100644 --- a/mono/mini/interp/interp.c +++ b/mono/mini/interp/interp.c @@ -1080,9 +1080,15 @@ interp_throw (ThreadContext *context, MonoException *ex, InterpFrame *frame, con g_assert (context->has_resume_state); } +// We conservatively pin exception object here to avoid tweaking the +// numerous call sites of this macro, even though, in a few cases, +// this is not needed. #define THROW_EX_GENERAL(exception,ex_ip, rethrow) \ do { \ - interp_throw (context, (exception), (frame), (ex_ip), (rethrow)); \ + MonoException *__ex = (exception); \ + MONO_HANDLE_ASSIGN_RAW (tmp_handle, (MonoObject*)__ex); \ + interp_throw (context, __ex, (frame), (ex_ip), (rethrow)); \ + MONO_HANDLE_ASSIGN_RAW (tmp_handle, (MonoObject*)NULL); \ goto resume; \ } while (0) @@ -1146,12 +1152,12 @@ ves_array_calculate_index (MonoArray *ao, stackval *sp, gboolean safe) guint32 pos = 0; if (ao->bounds) { for (gint32 i = 0; i < m_class_get_rank (ac); i++) { - guint32 idx = sp [i].data.i; - guint32 lower = ao->bounds [i].lower_bound; + gint32 idx = sp [i].data.i; + gint32 lower = ao->bounds [i].lower_bound; guint32 len = ao->bounds [i].length; - if (safe && (idx < lower || (idx - lower) >= len)) + if (safe && (idx < lower || (guint32)(idx - lower) >= len)) return -1; - pos = (pos * len) + idx - lower; + pos = (pos * len) + (guint32)(idx - lower); } } else { pos = sp [0].data.i; @@ -3264,11 +3270,10 @@ mono_interp_box_nullable (InterpFrame* frame, const guint16* ip, stackval* sp, M } static int -mono_interp_box_vt (InterpFrame* frame, const guint16* ip, stackval* sp) +mono_interp_box_vt (InterpFrame* frame, const guint16* ip, stackval* sp, MonoObjectHandle tmp_handle) { InterpMethod* const imethod = frame->imethod; - MonoObject* o; // See the comment about GC safety. MonoVTable * const vtable = (MonoVTable*)imethod->data_items [ip [1]]; MonoClass* const c = vtable->klass; @@ -3277,26 +3282,27 @@ mono_interp_box_vt (InterpFrame* frame, const guint16* ip, stackval* sp) guint16 offset = ip [2]; guint16 pop_vt_sp = !ip [3]; - OBJREF (o) = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); + MonoObject* o = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); + MONO_HANDLE_ASSIGN_RAW (tmp_handle, o); mono_value_copy_internal (mono_object_get_data (o), sp [-1 - offset].data.p, c); + MONO_HANDLE_ASSIGN_RAW (tmp_handle, NULL); - sp [-1 - offset].data.p = o; + sp [-1 - offset].data.o = o; return pop_vt_sp ? ALIGN_TO (size, MINT_VT_ALIGNMENT) : 0; } static void -mono_interp_box (InterpFrame* frame, const guint16* ip, stackval* sp) +mono_interp_box (InterpFrame* frame, const guint16* ip, stackval* sp, MonoObjectHandle tmp_handle) { - MonoObject *o; // See the comment about GC safety. MonoVTable * const vtable = (MonoVTable*)frame->imethod->data_items [ip [1]]; - - OBJREF (o) = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); - guint16 const offset = ip [2]; + MonoObject *o = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); + MONO_HANDLE_ASSIGN_RAW (tmp_handle, o); stackval_to_data (m_class_get_byval_arg (vtable->klass), &sp [-1 - offset], mono_object_get_data (o), FALSE); + MONO_HANDLE_ASSIGN_RAW (tmp_handle, NULL); - sp [-1 - offset].data.p = o; + sp [-1 - offset].data.o = o; } static int @@ -3436,6 +3442,24 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs }; #endif + HANDLE_FUNCTION_ENTER (); + /* + * GC SAFETY: + * + * The interpreter executes in gc unsafe (non-preempt) mode. On wasm, we cannot rely on + * scanning the stack or any registers. In order to make the code GC safe, every objref + * handled by the code needs to be kept alive and pinned in any of the following ways: + * - the object needs to be stored on the interpreter stack. In order to make sure the + * object actually gets stored on the interp stack and the store is not optimized out, + * the store/variable should be volatile. + * - if the execution of an opcode requires an object not coming from interp stack to be + * kept alive, the tmp_handle below can be used. This handle will keep only one object + * pinned by the GC. Ideally, once this object is no longer needed, the handle should be + * cleared. If we will need to have more objects pinned simultaneously, additional handles + * can be reserved here. + */ + MonoObjectHandle tmp_handle = MONO_HANDLE_NEW (MonoObject, NULL); + if (method_entry (context, frame, #if DEBUG_INTERP &tracing, @@ -3823,6 +3847,7 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs frame->state.ip = ip + 6; ves_pinvoke_method (csignature, (MonoFuncV)code, context, frame, &retval, save_last_error, cache, sp); + EXCEPTION_CHECKPOINT_GC_UNSAFE; CHECK_RESUME_STATE (context); if (csignature->ret->type != MONO_TYPE_VOID) { @@ -5123,7 +5148,6 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs MINT_IN_CASE(MINT_NEWOBJ_FAST) { MonoVTable *vtable = (MonoVTable*) frame->imethod->data_items [ip [3]]; INIT_VTABLE (vtable); - MonoObject *o; // See the comment about GC safety. guint16 param_count; guint16 imethod_index = ip [1]; @@ -5137,7 +5161,7 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs memmove (sp + 2, sp, param_count * sizeof (stackval)); } - OBJREF (o) = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); + MonoObject *o = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass)); if (G_UNLIKELY (!o)) { mono_error_set_out_of_memory (error, "Could not allocate %i bytes", m_class_get_instance_size (vtable->klass)); THROW_EX (mono_error_convert_to_exception (error), ip); @@ -5241,13 +5265,13 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs THROW_EX (exc, ip); } error_init_reuse (error); - MonoObject* o = NULL; // See the comment about GC safety. - OBJREF (o) = mono_object_new_checked (domain, newobj_class, error); + MonoObject* o = mono_object_new_checked (domain, newobj_class, error); + sp [0].data.o = o; // return value + sp [1].data.o = o; // first parameter + mono_error_cleanup (error); // FIXME: do not swallow the error error_init_reuse (error); EXCEPTION_CHECKPOINT; - sp [0].data.o = o; // return value - sp [1].data.o = o; // first parameter #ifndef DISABLE_REMOTING if (mono_object_is_transparent_proxy (o)) { MonoMethod *remoting_invoke_method = mono_marshal_get_remoting_invoke_with_check (cmethod->method, error); @@ -5946,12 +5970,12 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs MINT_IN_BREAK; } MINT_IN_CASE(MINT_BOX) { - mono_interp_box (frame, ip, sp); + mono_interp_box (frame, ip, sp, tmp_handle); ip += 3; MINT_IN_BREAK; } MINT_IN_CASE(MINT_BOX_VT) { - vt_sp -= mono_interp_box_vt (frame, ip, sp); + vt_sp -= mono_interp_box_vt (frame, ip, sp, tmp_handle); ip += 4; MINT_IN_BREAK; } @@ -6078,12 +6102,12 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs g_assert (ao->bounds); guint32 pos = 0; for (int i = 0; i < rank; i++) { - guint32 idx = sp [i].data.i; - guint32 lower = ao->bounds [i].lower_bound; + gint32 idx = sp [i].data.i; + gint32 lower = ao->bounds [i].lower_bound; guint32 len = ao->bounds [i].length; - if (idx < lower || (idx - lower) >= len) + if (idx < lower || (guint32)(idx - lower) >= len) THROW_EX (mono_get_exception_index_out_of_range (), ip); - pos = (pos * len) + idx - lower; + pos = (pos * len) + (guint32)(idx - lower); } sp [-1].data.p = mono_array_addr_with_size_fast (ao, esize, pos); @@ -7381,6 +7405,8 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs context->stack_pointer = (guchar*)frame->stack; DEBUG_LEAVE (); + + HANDLE_FUNCTION_RETURN (); } static void diff --git a/mono/mini/interp/mintops.h b/mono/mini/interp/mintops.h index 4a6f760f3b50..fe264b58ff6f 100644 --- a/mono/mini/interp/mintops.h +++ b/mono/mini/interp/mintops.h @@ -59,6 +59,8 @@ typedef enum { #define MINT_IS_MOVLOC(op) ((op) >= MINT_MOVLOC_1 && (op) <= MINT_MOVLOC_VT) #define MINT_IS_STLOC_NP(op) ((op) >= MINT_STLOC_NP_I4 && (op) <= MINT_STLOC_NP_O) #define MINT_IS_CONDITIONAL_BRANCH(op) ((op) >= MINT_BRFALSE_I4 && (op) <= MINT_BLT_UN_R8_S) +#define MINT_IS_UNOP_CONDITIONAL_BRANCH(op) ((op) >= MINT_BRFALSE_I4 && (op) <= MINT_BRTRUE_R8_S) +#define MINT_IS_BINOP_CONDITIONAL_BRANCH(op) ((op) >= MINT_BEQ_I4 && (op) <= MINT_BLT_UN_R8_S) #define MINT_IS_CALL(op) ((op) >= MINT_CALL && (op) <= MINT_JIT_CALL) #define MINT_IS_PATCHABLE_CALL(op) ((op) >= MINT_CALL && (op) <= MINT_VCALL) #define MINT_IS_NEWOBJ(op) ((op) >= MINT_NEWOBJ && (op) <= MINT_NEWOBJ_MAGIC) diff --git a/mono/mini/interp/transform.c b/mono/mini/interp/transform.c index 1fd13dd4bec3..e35976db2640 100644 --- a/mono/mini/interp/transform.c +++ b/mono/mini/interp/transform.c @@ -326,6 +326,51 @@ interp_prev_ins (InterpInst *ins) #endif +#define SET_SIMPLE_TYPE(s, ty) \ + do { \ + (s)->type = (ty); \ + (s)->flags = 0; \ + (s)->klass = NULL; \ + } while (0) + +#define SET_TYPE(s, ty, k) \ + do { \ + (s)->type = (ty); \ + (s)->flags = 0; \ + (s)->klass = k; \ + } while (0) + +#define REALLOC_STACK(td, sppos) \ + do { \ + (td)->stack_capacity *= 2; \ + (td)->stack = (StackInfo*)realloc ((td)->stack, (td)->stack_capacity * sizeof (td->stack [0])); \ + (td)->sp = (td)->stack + (sppos); \ + } while (0); + +#define PUSH_SIMPLE_TYPE(td, ty) \ + do { \ + int sp_height; \ + (td)->sp++; \ + sp_height = (td)->sp - (td)->stack; \ + if (sp_height > (td)->max_stack_height) \ + (td)->max_stack_height = sp_height; \ + if (sp_height > (td)->stack_capacity) \ + REALLOC_STACK(td, sp_height); \ + SET_SIMPLE_TYPE((td)->sp - 1, ty); \ + } while (0) + +#define PUSH_TYPE(td, ty, k) \ + do { \ + int sp_height; \ + (td)->sp++; \ + sp_height = (td)->sp - (td)->stack; \ + if (sp_height > (td)->max_stack_height) \ + (td)->max_stack_height = sp_height; \ + if (sp_height > (td)->stack_capacity) \ + REALLOC_STACK(td, sp_height); \ + SET_TYPE((td)->sp - 1, ty, k); \ + } while (0) + static void handle_branch (TransformData *td, int short_op, int long_op, int offset) @@ -376,23 +421,31 @@ two_arg_branch(TransformData *td, int mint_op, int offset) { int type1 = td->sp [-1].type == STACK_TYPE_O || td->sp [-1].type == STACK_TYPE_MP ? STACK_TYPE_I : td->sp [-1].type; int type2 = td->sp [-2].type == STACK_TYPE_O || td->sp [-2].type == STACK_TYPE_MP ? STACK_TYPE_I : td->sp [-2].type; - int long_op = mint_op + type1 - STACK_TYPE_I4; - int short_op = long_op + MINT_BEQ_I4_S - MINT_BEQ_I4; CHECK_STACK(td, 2); + if (type1 == STACK_TYPE_I4 && type2 == STACK_TYPE_I8) { // The il instruction starts with the actual branch, and not with the conversion opcodes interp_insert_ins (td, td->last_ins, MINT_CONV_I8_I4); + SET_SIMPLE_TYPE (td->sp - 1, STACK_TYPE_I8); + type1 = STACK_TYPE_I8; } else if (type1 == STACK_TYPE_I8 && type2 == STACK_TYPE_I4) { interp_insert_ins (td, td->last_ins, MINT_CONV_I8_I4_SP); + SET_SIMPLE_TYPE (td->sp - 2, STACK_TYPE_I8); } else if (type1 == STACK_TYPE_R4 && type2 == STACK_TYPE_R8) { interp_insert_ins (td, td->last_ins, MINT_CONV_R8_R4); + SET_SIMPLE_TYPE (td->sp - 1, STACK_TYPE_R8); + type1 = STACK_TYPE_R8; } else if (type1 == STACK_TYPE_R8 && type2 == STACK_TYPE_R4) { interp_insert_ins (td, td->last_ins, MINT_CONV_R8_R4_SP); + SET_SIMPLE_TYPE (td->sp - 2, STACK_TYPE_R8); } else if (type1 != type2) { g_warning("%s.%s: branch type mismatch %d %d", m_class_get_name (td->method->klass), td->method->name, td->sp [-1].type, td->sp [-2].type); } + + int long_op = mint_op + type1 - STACK_TYPE_I4; + int short_op = long_op + MINT_BEQ_I4_S - MINT_BEQ_I4; td->sp -= 2; handle_branch (td, short_op, long_op, offset); } @@ -470,51 +523,6 @@ can_store (int st_value, int vt_value) return st_value == vt_value; } -#define SET_SIMPLE_TYPE(s, ty) \ - do { \ - (s)->type = (ty); \ - (s)->flags = 0; \ - (s)->klass = NULL; \ - } while (0) - -#define SET_TYPE(s, ty, k) \ - do { \ - (s)->type = (ty); \ - (s)->flags = 0; \ - (s)->klass = k; \ - } while (0) - -#define REALLOC_STACK(td, sppos) \ - do { \ - (td)->stack_capacity *= 2; \ - (td)->stack = (StackInfo*)realloc ((td)->stack, (td)->stack_capacity * sizeof (td->stack [0])); \ - (td)->sp = (td)->stack + (sppos); \ - } while (0); - -#define PUSH_SIMPLE_TYPE(td, ty) \ - do { \ - int sp_height; \ - (td)->sp++; \ - sp_height = (td)->sp - (td)->stack; \ - if (sp_height > (td)->max_stack_height) \ - (td)->max_stack_height = sp_height; \ - if (sp_height > (td)->stack_capacity) \ - REALLOC_STACK(td, sp_height); \ - SET_SIMPLE_TYPE((td)->sp - 1, ty); \ - } while (0) - -#define PUSH_TYPE(td, ty, k) \ - do { \ - int sp_height; \ - (td)->sp++; \ - sp_height = (td)->sp - (td)->stack; \ - if (sp_height > (td)->max_stack_height) \ - (td)->max_stack_height = sp_height; \ - if (sp_height > (td)->stack_capacity) \ - REALLOC_STACK(td, sp_height); \ - SET_TYPE((td)->sp - 1, ty, k); \ - } while (0) - static void move_stack (TransformData *td, int start, int amount) { @@ -988,18 +996,15 @@ interp_method_get_header (MonoMethod* method, MonoError *error) static void emit_store_value_as_local (TransformData *td, MonoType *src) { - int size = mini_magic_type_size (NULL, src); int local = create_interp_local (td, mini_native_type_replace_type (src)); store_local (td, local); - size = ALIGN_TO (size, MINT_VT_ALIGNMENT); - interp_add_ins (td, MINT_LDLOC_VT); + interp_add_ins (td, MINT_LDLOCA_S); td->last_ins->data [0] = local; - WRITE32_INS (td->last_ins, 1, &size); + td->locals [local].indirects++; - PUSH_VT (td, size); - PUSH_TYPE (td, STACK_TYPE_VT, NULL); + PUSH_SIMPLE_TYPE (td, STACK_TYPE_MP); } // Returns whether we can optimize away the instructions starting at start. @@ -1200,9 +1205,11 @@ interp_emit_ldelema (TransformData *td, MonoClass *array_class, MonoClass *check int rank = m_class_get_rank (array_class); int size = mono_class_array_element_size (element_class); + gboolean bounded = m_class_get_byval_arg (array_class) ? m_class_get_byval_arg (array_class)->type == MONO_TYPE_ARRAY : FALSE; + // We only need type checks when writing to array of references if (!check_class || m_class_is_valuetype (element_class)) { - if (rank == 1) { + if (rank == 1 && !bounded) { interp_add_ins (td, MINT_LDELEMA1); WRITE32_INS (td->last_ins, 0, &size); } else { @@ -1367,10 +1374,11 @@ interp_handle_magic_type_intrinsics (TransformData *td, MonoMethod *target_metho return TRUE; } else if (!strcmp ("CompareTo", tm) || !strcmp ("Equals", tm)) { MonoType *arg = csignature->params [0]; + int mt = mint_type (arg); /* on 'System.n*::{CompareTo,Equals} (System.n*)' variant we need to push managed * pointer instead of value */ - if (arg->type == MONO_TYPE_VALUETYPE) + if (mt != MINT_TYPE_O) emit_store_value_as_local (td, arg); /* emit call to managed conversion method */ @@ -4941,6 +4949,20 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, klass = mini_get_class (method, token, generic_context); CHECK_TYPELOAD (klass); + // Common in generic code: + // box T + unbox.any T -> nop + if ((td->last_ins->opcode == MINT_BOX || td->last_ins->opcode == MINT_BOX_VT) && + (td->sp - 1)->klass == klass && !td->is_bb_start[in_offset]) { + gboolean is_vt = td->last_ins->opcode == MINT_BOX_VT; + interp_clear_ins(td, td->last_ins); + if (is_vt) + PUSH_VT(td, mono_class_value_size(klass, NULL)); + int mt = mint_type(m_class_get_byval_arg(klass)); + SET_TYPE(td->sp - 1, stack_type[mt], klass); + td->ip += 5; + break; + } + if (mini_type_is_reference (m_class_get_byval_arg (klass))) { int mt = mint_type (m_class_get_byval_arg (klass)); interp_handle_isinst (td, klass, FALSE); @@ -6111,12 +6133,24 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, } case CEE_MONO_LDPTR: case CEE_MONO_CLASSCONST: + case CEE_MONO_METHODCONST: token = read32 (td->ip + 1); td->ip += 5; interp_add_ins (td, MINT_MONO_LDPTR); td->last_ins->data [0] = get_data_item_index (td, mono_method_get_wrapper_data (method, token)); PUSH_SIMPLE_TYPE (td, STACK_TYPE_I); break; + case CEE_MONO_PINVOKE_ADDR_CACHE: { + token = read32 (td->ip + 1); + td->ip += 5; + interp_add_ins (td, MINT_MONO_LDPTR); + g_assert (method->wrapper_type != MONO_WRAPPER_NONE); + /* This is a memory slot used by the wrapper */ + gpointer addr = mono_domain_alloc0 (td->rtm->domain, sizeof (gpointer)); + td->last_ins->data [0] = get_data_item_index (td, addr); + PUSH_SIMPLE_TYPE (td, STACK_TYPE_I); + break; + } case CEE_MONO_OBJADDR: CHECK_STACK (td, 1); ++td->ip; @@ -7138,6 +7172,42 @@ interp_fold_unop (TransformData *td, StackContentInfo *sp, InterpInst *ins) return ins; } +#define INTERP_FOLD_UNOP_BR(_opcode,_stack_type,_cond) \ + case _opcode: \ + g_assert (sp->val.type == _stack_type); \ + if (_cond) \ + ins->opcode = MINT_BR_S; \ + else \ + interp_clear_ins (td, ins); \ + break; + +static InterpInst* +interp_fold_unop_cond_br (TransformData *td, StackContentInfo *sp, InterpInst *ins) +{ + sp--; + // If we can't remove the instruction pushing the constant, don't bother + if (sp->ins == NULL) + return ins; + if (sp->val.type != STACK_VALUE_I4 && sp->val.type != STACK_VALUE_I8) + return ins; + // Top of the stack is a constant + switch (ins->opcode) { + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I4_S, STACK_VALUE_I4, sp [0].val.i == 0); + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I8_S, STACK_VALUE_I8, sp [0].val.l == 0); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I4_S, STACK_VALUE_I4, sp [0].val.i != 0); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I8_S, STACK_VALUE_I8, sp [0].val.l != 0); + + default: + return ins; + } + + mono_interp_stats.constant_folds++; + mono_interp_stats.killed_instructions++; + interp_clear_ins (td, sp->ins); + sp->val.type = STACK_VALUE_NONE; + return ins; +} + #define INTERP_FOLD_BINOP(opcode,stack_type,field,op) \ case opcode: \ g_assert (sp [0].val.type == stack_type && sp [1].val.type == stack_type); \ @@ -7272,6 +7342,63 @@ interp_fold_binop (TransformData *td, StackContentInfo *sp, InterpInst *ins) return ins; } +#define INTERP_FOLD_BINOP_BR(_opcode,_stack_type,_cond) \ + case _opcode: \ + g_assert (sp [0].val.type == _stack_type); \ + g_assert (sp [1].val.type == _stack_type); \ + if (_cond) \ + ins->opcode = MINT_BR_S; \ + else \ + interp_clear_ins (td, ins); \ + break; + +static InterpInst* +interp_fold_binop_cond_br (TransformData *td, StackContentInfo *sp, InterpInst *ins) +{ + sp -= 2; + // If we can't remove the instructions pushing the constants, don't bother + if (sp [0].ins == NULL || sp [1].ins == NULL) + return ins; + if (sp [0].val.type != STACK_VALUE_I4 && sp [0].val.type != STACK_VALUE_I8) + return ins; + if (sp [1].val.type != STACK_VALUE_I4 && sp [1].val.type != STACK_VALUE_I8) + return ins; + + switch (ins->opcode) { + INTERP_FOLD_BINOP_BR (MINT_BEQ_I4_S, STACK_VALUE_I4, sp [0].val.i == sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BEQ_I8_S, STACK_VALUE_I8, sp [0].val.l == sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BGE_I4_S, STACK_VALUE_I4, sp [0].val.i >= sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BGE_I8_S, STACK_VALUE_I8, sp [0].val.l >= sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BGT_I4_S, STACK_VALUE_I4, sp [0].val.i > sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BGT_I8_S, STACK_VALUE_I8, sp [0].val.l > sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BLT_I4_S, STACK_VALUE_I4, sp [0].val.i < sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BLT_I8_S, STACK_VALUE_I8, sp [0].val.l < sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BLE_I4_S, STACK_VALUE_I4, sp [0].val.i <= sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BLE_I8_S, STACK_VALUE_I8, sp [0].val.l <= sp [1].val.l); + + INTERP_FOLD_BINOP_BR (MINT_BNE_UN_I4_S, STACK_VALUE_I4, sp [0].val.i != sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BNE_UN_I8_S, STACK_VALUE_I8, sp [0].val.l != sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BGE_UN_I4_S, STACK_VALUE_I4, (guint32)sp [0].val.i >= (guint32)sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BGE_UN_I8_S, STACK_VALUE_I8, (guint64)sp [0].val.l >= (guint64)sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BGT_UN_I4_S, STACK_VALUE_I4, (guint32)sp [0].val.i > (guint32)sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BGT_UN_I8_S, STACK_VALUE_I8, (guint64)sp [0].val.l > (guint64)sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BLE_UN_I4_S, STACK_VALUE_I4, (guint32)sp [0].val.i <= (guint32)sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BLE_UN_I8_S, STACK_VALUE_I8, (guint64)sp [0].val.l <= (guint64)sp [1].val.l); + INTERP_FOLD_BINOP_BR (MINT_BLT_UN_I4_S, STACK_VALUE_I4, (guint32)sp [0].val.i < (guint32)sp [1].val.i); + INTERP_FOLD_BINOP_BR (MINT_BLT_UN_I8_S, STACK_VALUE_I8, (guint64)sp [0].val.l < (guint64)sp [1].val.l); + + default: + return ins; + } + mono_interp_stats.constant_folds++; + mono_interp_stats.killed_instructions += 2; + interp_clear_ins (td, sp [0].ins); + interp_clear_ins (td, sp [1].ins); + sp [0].val.type = STACK_VALUE_NONE; + sp [1].val.type = STACK_VALUE_NONE; + return ins; +} + static gboolean interp_local_equal (StackValue *locals, int local1, int local2) { @@ -7371,36 +7498,40 @@ interp_cprop (TransformData *td) mono_interp_stats.killed_instructions++; } } - } else if (locals [loaded_local].type == STACK_VALUE_LOCAL) { - g_assert (!td->locals [loaded_local].indirects); - // do copy propagation of the original source - mono_interp_stats.copy_propagations++; - local_ref_count [loaded_local]--; - // We can't propagate a local that has its address taken - g_assert (!td->locals [locals [loaded_local].local].indirects); - ins->data [0] = locals [loaded_local].local; - local_ref_count [ins->data [0]]++; - if (td->verbose_level) { - g_print ("cprop loc %d -> loc %d :\n\t", loaded_local, locals [loaded_local].local); - dump_interp_inst_newline (ins); - } - } else if (locals [loaded_local].type == STACK_VALUE_I4 || locals [loaded_local].type == STACK_VALUE_I8) { - gboolean is_i4 = locals [loaded_local].type == STACK_VALUE_I4; - g_assert (!td->locals [loaded_local].indirects); - if (is_i4) - ins = interp_get_ldc_i4_from_const (td, ins, locals [loaded_local].i); - else - ins = interp_inst_replace_with_i8_const (td, ins, locals [loaded_local].l); - sp->ins = ins; - sp->val = locals [loaded_local]; - local_ref_count [loaded_local]--; - mono_interp_stats.copy_propagations++; - if (td->verbose_level) { - g_print ("cprop loc %d -> ct :\n\t", loaded_local); - dump_interp_inst_newline (ins); + } + /* If we didn't replace this ldloc with a stloc.np, try other optimizations */ + if (!replace_op) { + if (locals [loaded_local].type == STACK_VALUE_LOCAL) { + g_assert (!td->locals [loaded_local].indirects); + // do copy propagation of the original source + mono_interp_stats.copy_propagations++; + local_ref_count [loaded_local]--; + // We can't propagate a local that has its address taken + g_assert (!td->locals [locals [loaded_local].local].indirects); + ins->data [0] = locals [loaded_local].local; + local_ref_count [ins->data [0]]++; + if (td->verbose_level) { + g_print ("cprop loc %d -> loc %d :\n\t", loaded_local, locals [loaded_local].local); + dump_interp_inst_newline (ins); + } + } else if (locals [loaded_local].type == STACK_VALUE_I4 || locals [loaded_local].type == STACK_VALUE_I8) { + gboolean is_i4 = locals [loaded_local].type == STACK_VALUE_I4; + g_assert (!td->locals [loaded_local].indirects); + if (is_i4) + ins = interp_get_ldc_i4_from_const (td, ins, locals [loaded_local].i); + else + ins = interp_inst_replace_with_i8_const (td, ins, locals [loaded_local].l); + sp->ins = ins; + sp->val = locals [loaded_local]; + local_ref_count [loaded_local]--; + mono_interp_stats.copy_propagations++; + if (td->verbose_level) { + g_print ("cprop loc %d -> ct :\n\t", loaded_local); + dump_interp_inst_newline (ins); + } + // FIXME this replace_op got ugly + replace_op = ins->opcode; } - // FIXME this replace_op got ugly - replace_op = ins->opcode; } if (!replace_op) { // Save the ldloc on the stack if it wasn't optimized away @@ -7568,9 +7699,16 @@ interp_cprop (TransformData *td) } else if (ins->opcode == MINT_CASTCLASS || ins->opcode == MINT_CASTCLASS_COMMON || ins->opcode == MINT_CASTCLASS_INTERFACE) { // Keep the value on the stack, but prevent optimizing away sp [-1].ins = NULL; - } else if (MINT_IS_CONDITIONAL_BRANCH (ins->opcode)) { - sp -= pop; - g_assert (push == 0); + } else if (MINT_IS_UNOP_CONDITIONAL_BRANCH (ins->opcode)) { + ins = interp_fold_unop_cond_br (td, sp, ins); + sp--; + // We can't clear any instruction that pushes the stack, because the + // branched code will expect a certain stack size. + for (StackContentInfo *sp_iter = stack; sp_iter < sp; sp_iter++) + sp_iter->ins = NULL; + } else if (MINT_IS_BINOP_CONDITIONAL_BRANCH (ins->opcode)) { + ins = interp_fold_binop_cond_br (td, sp, ins); + sp -= 2; // We can't clear any instruction that pushes the stack, because the // branched code will expect a certain stack size. for (StackContentInfo *sp_iter = stack; sp_iter < sp; sp_iter++) diff --git a/mono/mini/intrinsics.c b/mono/mini/intrinsics.c index cf8bcc936aaa..912e12e427d6 100644 --- a/mono/mini/intrinsics.c +++ b/mono/mini/intrinsics.c @@ -31,7 +31,7 @@ emit_array_generic_access (MonoCompile *cfg, MonoMethodSignature *fsig, MonoInst MonoClass *eklass = mono_class_from_mono_type_internal (fsig->params [1]); /* the bounds check is already done by the callers */ - addr = mini_emit_ldelema_1_ins (cfg, eklass, args [0], args [1], FALSE); + addr = mini_emit_ldelema_1_ins (cfg, eklass, args [0], args [1], FALSE, FALSE); MonoType *etype = m_class_get_byval_arg (eklass); if (is_set) { EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, load, etype, args [2]->dreg, 0); @@ -2005,7 +2005,7 @@ emit_array_unsafe_access (MonoCompile *cfg, MonoMethodSignature *fsig, MonoInst if (is_set) { return mini_emit_array_store (cfg, eklass, args, FALSE); } else { - MonoInst *ins, *addr = mini_emit_ldelema_1_ins (cfg, eklass, args [0], args [1], FALSE); + MonoInst *ins, *addr = mini_emit_ldelema_1_ins (cfg, eklass, args [0], args [1], FALSE, FALSE); EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (eklass), addr->dreg, 0); return ins; } diff --git a/mono/mini/main.c b/mono/mini/main.c index b857ec3ca4d0..7cc3fecfa17d 100644 --- a/mono/mini/main.c +++ b/mono/mini/main.c @@ -35,6 +35,9 @@ # include "buildver-boehm.h" # endif #endif +#ifdef TARGET_OSX +#include +#endif //#define TEST_ICALL_SYMBOL_MAP 1 @@ -179,8 +182,68 @@ probe_embedded (const char *program, int *ref_argc, char **ref_argv []) goto doclose; if (read (fd, sigbuffer, sizeof (sigbuffer)) == -1) goto doclose; - if (memcmp (sigbuffer+sizeof(uint64_t), "xmonkeysloveplay", 16) != 0) - goto doclose; + // First, see if "xmonkeysloveplay" is at the end of file + if (memcmp (sigbuffer + sizeof (uint64_t), "xmonkeysloveplay", 16) == 0) + goto found; + +#ifdef TARGET_OSX + { + /* + * If "xmonkeysloveplay" is not at the end of file, + * on Mac OS X, we try a little harder, by actually + * reading the binary's header structure, to see + * if it is located at the end of a LC_SYMTAB section. + * + * This is because Apple code-signing appends a + * LC_CODE_SIGNATURE section to the binary, so + * for a signed binary, "xmonkeysloveplay" is no + * longer at the end of file. + * + * The rest is sanity-checks for the header and section structures. + */ + struct mach_header_64 bin_header; + if ((sigstart = lseek (fd, 0, SEEK_SET)) == -1) + goto doclose; + // Find and check binary header + if (read (fd, &bin_header, sizeof (bin_header)) == -1) + goto doclose; + if (bin_header.magic != MH_MAGIC_64) + goto doclose; + + off_t total = bin_header.sizeofcmds; + uint32_t count = bin_header.ncmds; + while (total > 0 && count > 0) { + struct load_command lc; + off_t sig_stored = lseek (fd, 0, SEEK_CUR); // get current offset + if (read (fd, &lc, sizeof (lc)) == -1) + goto doclose; + if (lc.cmd == LC_SYMTAB) { + struct symtab_command stc; + if ((sigstart = lseek (fd, -sizeof (lc), SEEK_CUR)) == -1) + goto doclose; + if (read (fd, &stc, sizeof (stc)) == -1) + goto doclose; + + // Check the end of the LC_SYMTAB section for "xmonkeysloveplay" + if ((sigstart = lseek (fd, -(16 + sizeof (uint64_t)) + stc.stroff + stc.strsize, SEEK_SET)) == -1) + goto doclose; + if (read (fd, sigbuffer, sizeof (sigbuffer)) == -1) + goto doclose; + if (memcmp (sigbuffer + sizeof (uint64_t), "xmonkeysloveplay", 16) == 0) + goto found; + } + if ((sigstart = lseek (fd, sig_stored + lc.cmdsize, SEEK_SET)) == -1) + goto doclose; + total -= sizeof (lc.cmdsize); + count--; + } + } +#endif + + // did not find "xmonkeysloveplay" at end of file or end of LC_SYMTAB section + goto doclose; + +found: directory_location = GUINT64_FROM_LE ((*(uint64_t *) &sigbuffer [0])); if (lseek (fd, directory_location, SEEK_SET) == -1) goto doclose; diff --git a/mono/mini/method-to-ir.c b/mono/mini/method-to-ir.c index ead3c2e63b2d..c5125df0c9f9 100644 --- a/mono/mini/method-to-ir.c +++ b/mono/mini/method-to-ir.c @@ -4082,11 +4082,11 @@ mini_emit_sext_index_reg (MonoCompile *cfg, MonoInst *index) } MonoInst* -mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, MonoInst *index, gboolean bcheck) +mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, MonoInst *index, gboolean bcheck, gboolean bounded) { MonoInst *ins; guint32 size; - int mult_reg, add_reg, array_reg, index2_reg; + int mult_reg, add_reg, array_reg, index2_reg, bounds_reg, lower_bound_reg, realidx2_reg; int context_used; if (mini_is_gsharedvt_variable_klass (klass)) { @@ -4099,16 +4099,37 @@ mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, Mono mult_reg = alloc_preg (cfg); array_reg = arr->dreg; - index2_reg = mini_emit_sext_index_reg (cfg, index); + realidx2_reg = index2_reg = mini_emit_sext_index_reg (cfg, index); + + if (bounded) { + bounds_reg = alloc_preg (cfg); + lower_bound_reg = alloc_preg (cfg); + realidx2_reg = alloc_preg (cfg); + + MonoBasicBlock *is_null_bb = NULL; + NEW_BBLOCK (cfg, is_null_bb); + + // gint32 lower_bound = 0; + // if (arr->bounds) + // lower_bound = arr->bounds.lower_bound; + // realidx2 = index2 - lower_bound; + MONO_EMIT_NEW_PCONST (cfg, lower_bound_reg, NULL); + MONO_EMIT_NEW_LOAD_MEMBASE (cfg, bounds_reg, arr->dreg, MONO_STRUCT_OFFSET (MonoArray, bounds)); + MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, bounds_reg, 0); + MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBEQ, is_null_bb); + MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, lower_bound_reg, bounds_reg, MONO_STRUCT_OFFSET (MonoArrayBounds, lower_bound)); + MONO_START_BB (cfg, is_null_bb); + MONO_EMIT_NEW_BIALU (cfg, OP_PSUB, realidx2_reg, index2_reg, lower_bound_reg); + } if (bcheck) - MONO_EMIT_BOUNDS_CHECK (cfg, array_reg, MonoArray, max_length, index2_reg); + MONO_EMIT_BOUNDS_CHECK (cfg, array_reg, MonoArray, max_length, realidx2_reg); #if defined(TARGET_X86) || defined(TARGET_AMD64) if (size == 1 || size == 2 || size == 4 || size == 8) { static const int fast_log2 [] = { 1, 0, 1, -1, 2, -1, -1, -1, 3 }; - EMIT_NEW_X86_LEA (cfg, ins, array_reg, index2_reg, fast_log2 [size], MONO_STRUCT_OFFSET (MonoArray, vector)); + EMIT_NEW_X86_LEA (cfg, ins, array_reg, realidx2_reg, fast_log2 [size], MONO_STRUCT_OFFSET (MonoArray, vector)); ins->klass = klass; ins->type = STACK_MP; @@ -4126,9 +4147,9 @@ mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, Mono context_used = mini_class_check_context_used (cfg, klass); g_assert (context_used); rgctx_ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE); - MONO_EMIT_NEW_BIALU (cfg, OP_IMUL, mult_reg, index2_reg, rgctx_ins->dreg); + MONO_EMIT_NEW_BIALU (cfg, OP_IMUL, mult_reg, realidx2_reg, rgctx_ins->dreg); } else { - MONO_EMIT_NEW_BIALU_IMM (cfg, OP_MUL_IMM, mult_reg, index2_reg, size); + MONO_EMIT_NEW_BIALU_IMM (cfg, OP_MUL_IMM, mult_reg, realidx2_reg, size); } MONO_EMIT_NEW_BIALU (cfg, OP_PADD, add_reg, array_reg, mult_reg); NEW_BIALU_IMM (cfg, ins, OP_PADD_IMM, add_reg, add_reg, MONO_STRUCT_OFFSET (MonoArray, vector)); @@ -4221,10 +4242,12 @@ mini_emit_ldelema_ins (MonoCompile *cfg, MonoMethod *cmethod, MonoInst **sp, guc int element_size; MonoClass *eclass = m_class_get_element_class (cmethod->klass); + gboolean bounded = m_class_get_byval_arg (cmethod->klass) ? m_class_get_byval_arg (cmethod->klass)->type == MONO_TYPE_ARRAY : FALSE; + rank = mono_method_signature_internal (cmethod)->param_count - (is_set? 1: 0); if (rank == 1) - return mini_emit_ldelema_1_ins (cfg, eclass, sp [0], sp [1], TRUE); + return mini_emit_ldelema_1_ins (cfg, eclass, sp [0], sp [1], TRUE, bounded); /* emit_ldelema_2 depends on OP_LMUL */ if (!cfg->backend->emulate_mul_div && rank == 2 && (cfg->opt & MONO_OPT_INTRINS) && !mini_is_gsharedvt_variable_klass (eclass)) { @@ -4277,7 +4300,7 @@ mini_emit_array_store (MonoCompile *cfg, MonoClass *klass, MonoInst **sp, gboole MonoInst *addr; // FIXME-VT: OP_ICONST optimization - addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE); + addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE, FALSE); EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), addr->dreg, 0, sp [2]->dreg); ins->opcode = OP_STOREV_MEMBASE; } else if (sp [1]->opcode == OP_ICONST) { @@ -4292,7 +4315,7 @@ mini_emit_array_store (MonoCompile *cfg, MonoClass *klass, MonoInst **sp, gboole MONO_EMIT_BOUNDS_CHECK (cfg, array_reg, MonoArray, max_length, index_reg); EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), array_reg, offset, sp [2]->dreg); } else { - MonoInst *addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], safety_checks); + MonoInst *addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], safety_checks, FALSE); if (!mini_debug_options.weak_memory_model && mini_class_is_reference (klass)) mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL); EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), addr->dreg, 0, sp [2]->dreg); @@ -6064,7 +6087,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b MonoBitSet *seq_point_set_locs = NULL; gboolean emitted_funccall_seq_point = FALSE; - cfg->disable_inline = is_jit_optimizer_disabled (method); + cfg->disable_inline = (method->iflags & METHOD_IMPL_ATTRIBUTE_NOOPTIMIZATION) || is_jit_optimizer_disabled (method); cfg->current_method = method; image = m_class_get_image (method->klass); @@ -10072,7 +10095,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b } readonly = FALSE; - ins = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE); + ins = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE, FALSE); *sp++ = ins; break; case MONO_CEE_LDELEM: @@ -10106,7 +10129,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b if (mini_is_gsharedvt_variable_klass (klass)) { // FIXME-VT: OP_ICONST optimization - addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE); + addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE, FALSE); EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), addr->dreg, 0); ins->opcode = OP_LOADV_MEMBASE; } else if (sp [1]->opcode == OP_ICONST) { @@ -10120,7 +10143,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b MONO_EMIT_BOUNDS_CHECK (cfg, array_reg, MonoArray, max_length, index_reg); EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), array_reg, offset); } else { - addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE); + addr = mini_emit_ldelema_1_ins (cfg, klass, sp [0], sp [1], TRUE, FALSE); EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, m_class_get_byval_arg (klass), addr->dreg, 0); } *sp++ = ins; @@ -10777,6 +10800,24 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b *sp++ = ins; inline_costs += CALL_COST * MIN(10, num_calls++); break; + case MONO_CEE_MONO_METHODCONST: + g_assert (method->wrapper_type != MONO_WRAPPER_NONE); + EMIT_NEW_METHODCONST (cfg, ins, mono_method_get_wrapper_data (method, token)); + *sp++ = ins; + break; + case MONO_CEE_MONO_PINVOKE_ADDR_CACHE: { + g_assert (method->wrapper_type != MONO_WRAPPER_NONE); + MonoMethod *pinvoke_method = (MonoMethod*)mono_method_get_wrapper_data (method, token); + /* This is a memory slot used by the wrapper */ + if (cfg->compile_aot) { + EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE, pinvoke_method); + } else { + gpointer addr = mono_domain_alloc0 (cfg->domain, sizeof (gpointer)); + EMIT_NEW_PCONST (cfg, ins, addr); + } + *sp++ = ins; + break; + } case MONO_CEE_MONO_NOT_TAKEN: g_assert (method->wrapper_type != MONO_WRAPPER_NONE); cfg->cbb->out_of_line = TRUE; diff --git a/mono/mini/mini-arm64.c b/mono/mini/mini-arm64.c index 30992b6cd881..317390249d67 100644 --- a/mono/mini/mini-arm64.c +++ b/mono/mini/mini-arm64.c @@ -4198,6 +4198,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) arm_uxthw (code, dreg, dreg); break; case OP_FCONV_TO_I4: + case OP_FCONV_TO_I: arm_fcvtzs_dx (code, dreg, sreg1); arm_sxtwx (code, dreg, dreg); break; diff --git a/mono/mini/mini-codegen.c b/mono/mini/mini-codegen.c index fcee745b5a78..5b26a6c1ec68 100644 --- a/mono/mini/mini-codegen.c +++ b/mono/mini/mini-codegen.c @@ -1748,7 +1748,8 @@ mono_local_regalloc (MonoCompile *cfg, MonoBasicBlock *bb) } if (spec [MONO_INST_CLOB] == 'c') { - int j, s, dreg, dreg2, cur_bank; + int j, dreg, dreg2, cur_bank; + regmask_t s; guint64 clob_mask; clob_mask = MONO_ARCH_CALLEE_REGS; diff --git a/mono/mini/mini-darwin.c b/mono/mini/mini-darwin.c index b0d2a4d674ed..834a60a13e72 100644 --- a/mono/mini/mini-darwin.c +++ b/mono/mini/mini-darwin.c @@ -79,6 +79,29 @@ mono_runtime_install_handlers (void) { mono_runtime_posix_install_handlers (); +#if !defined (HOST_WATCHOS) && !defined (HOST_TVOS) + /* LLDB installs task-wide Mach exception handlers. XNU dispatches Mach + * exceptions first to any registered "activation" handler and then to + * any registered task handler before dispatching the exception to a + * host-wide Mach exception handler that does translation to POSIX + * signals. This makes it impossible to use LLDB with an + * implicit-null-check-enabled Mono; continuing execution after LLDB + * traps an EXC_BAD_ACCESS will result in LLDB's EXC_BAD_ACCESS handler + * being invoked again. This also interferes with the translation of + * SIGFPEs to .NET-level ArithmeticExceptions. Work around this here by + * installing a no-op task-wide Mach exception handler for + * EXC_BAD_ACCESS and EXC_ARITHMETIC. + */ + kern_return_t kr = task_set_exception_ports ( + mach_task_self (), + EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, /* SIGSEGV, SIGFPE */ + MACH_PORT_NULL, + EXCEPTION_STATE_IDENTITY, + MACHINE_THREAD_STATE); + if (kr != KERN_SUCCESS) + g_warning ("mono_runtime_install_handlers: task_set_exception_ports failed"); +#endif + /* Snow Leopard has a horrible bug: http://openradar.appspot.com/7209349 * This causes obscure SIGTRAP's for any application that comes across this built on * Snow Leopard. This is a horrible hack to ensure that the private __CFInitialize diff --git a/mono/mini/mini-exceptions.c b/mono/mini/mini-exceptions.c index e75267929377..9f88c52baa2a 100644 --- a/mono/mini/mini-exceptions.c +++ b/mono/mini/mini-exceptions.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -1047,19 +1048,28 @@ ves_icall_get_trace (MonoException *exc, gint32 skip, MonoBoolean need_file_info return res; } + HANDLE_FUNCTION_ENTER (); + + MONO_HANDLE_PIN (ta); + len = mono_array_length_internal (ta) / TRACE_IP_ENTRY_SIZE; res = mono_array_new_checked (domain, mono_defaults.stack_frame_class, len > skip ? len - skip : 0, error); - if (mono_error_set_pending_exception (error)) - return NULL; + if (!is_ok (error)) + goto fail; + + MONO_HANDLE_PIN (res); + + MonoObjectHandle sf_h; + sf_h = MONO_HANDLE_NEW (MonoObject, NULL); for (i = skip; i < len; i++) { MonoJitInfo *ji; MonoStackFrame *sf = (MonoStackFrame *)mono_object_new_checked (domain, mono_defaults.stack_frame_class, error); - if (!is_ok (error)) { - mono_error_set_pending_exception (error); - return NULL; - } + if (!is_ok (error)) + goto fail; + MONO_HANDLE_ASSIGN_RAW (sf_h, sf); + ExceptionTraceIp trace_ip; memcpy (&trace_ip, mono_array_addr_fast (ta, ExceptionTraceIp, i), sizeof (ExceptionTraceIp)); gpointer ip = trace_ip.ip; @@ -1091,18 +1101,14 @@ ves_icall_get_trace (MonoException *exc, gint32 skip, MonoBoolean need_file_info s = mono_method_get_name_full (method, TRUE, FALSE, MONO_TYPE_NAME_FORMAT_REFLECTION); MonoString *name = mono_string_new_checked (domain, s, error); g_free (s); - if (!is_ok (error)) { - mono_error_set_pending_exception (error); - return NULL; - } + if (!is_ok (error)) + goto fail; MONO_OBJECT_SETREF_INTERNAL (sf, internal_method_name, name); } else { MonoReflectionMethod *rm = mono_method_get_object_checked (domain, method, NULL, error); - if (!is_ok (error)) { - mono_error_set_pending_exception (error); - return NULL; - } + if (!is_ok (error)) + goto fail; MONO_OBJECT_SETREF_INTERNAL (sf, method, rm); } @@ -1129,10 +1135,8 @@ ves_icall_get_trace (MonoException *exc, gint32 skip, MonoBoolean need_file_info if (need_file_info) { if (location && location->source_file) { MonoString *filename = mono_string_new_checked (domain, location->source_file, error); - if (!is_ok (error)) { - mono_error_set_pending_exception (error); - return NULL; - } + if (!is_ok (error)) + goto fail; MONO_OBJECT_SETREF_INTERNAL (sf, filename, filename); sf->line = location->row; sf->column = location->column; @@ -1145,8 +1149,13 @@ ves_icall_get_trace (MonoException *exc, gint32 skip, MonoBoolean need_file_info mono_debug_free_source_location (location); mono_array_setref_internal (res, i - skip, sf); } + goto exit; - return res; + fail: + mono_error_set_pending_exception (error); + res = NULL; + exit: + HANDLE_FUNCTION_RETURN_VAL (res); } static void diff --git a/mono/mini/mini-generic-sharing.c b/mono/mini/mini-generic-sharing.c index 5fb8bbb39c96..2ad21ecc1b56 100644 --- a/mono/mini/mini-generic-sharing.c +++ b/mono/mini/mini-generic-sharing.c @@ -1276,6 +1276,10 @@ get_wrapper_shared_type_full (MonoType *t, gboolean is_field) #else return m_class_get_byval_arg (mono_defaults.uint32_class); #endif + case MONO_TYPE_R4: + return m_class_get_byval_arg (mono_defaults.single_class); + case MONO_TYPE_R8: + return m_class_get_byval_arg (mono_defaults.double_class); case MONO_TYPE_OBJECT: case MONO_TYPE_CLASS: case MONO_TYPE_SZARRAY: @@ -4464,6 +4468,21 @@ mini_is_gsharedvt_sharable_inst (MonoGenericInst *inst) return has_vt; } +gboolean +mini_is_gsharedvt_inst (MonoGenericInst *inst) +{ + int i; + + for (i = 0; i < inst->type_argc; ++i) { + MonoType *type = inst->type_argv [i]; + + if (mini_is_gsharedvt_type (type)) + return TRUE; + } + + return FALSE; +} + gboolean mini_is_gsharedvt_sharable_method (MonoMethod *method) { diff --git a/mono/mini/mini-llvm.c b/mono/mini/mini-llvm.c index 4ac4e72eece2..5102826ce3b8 100644 --- a/mono/mini/mini-llvm.c +++ b/mono/mini/mini-llvm.c @@ -316,6 +316,12 @@ const_int32 (int v) return LLVMConstInt (LLVMInt32Type (), v, FALSE); } +static LLVMValueRef +const_int64 (int64_t v) +{ + return LLVMConstInt (LLVMInt64Type (), v, FALSE); +} + /* * IntPtrType: * @@ -5712,6 +5718,28 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) values [ins->dreg] = LLVMBuildAdd (builder, v2, LLVMConstInt (IntPtrType (), ins->inst_imm, FALSE), dname); break; } + case OP_X86_BSF32: + case OP_X86_BSF64: { + LLVMValueRef args [] = { + lhs, + LLVMConstInt (LLVMInt1Type (), 1, TRUE), + }; + int op = ins->opcode == OP_X86_BSF32 ? INTRINS_CTTZ_I32 : INTRINS_CTTZ_I64; + values [ins->dreg] = call_intrins (ctx, op, args, dname); + break; + } + case OP_X86_BSR32: + case OP_X86_BSR64: { + LLVMValueRef args [] = { + lhs, + LLVMConstInt (LLVMInt1Type (), 1, TRUE), + }; + int op = ins->opcode == OP_X86_BSR32 ? INTRINS_CTLZ_I32 : INTRINS_CTLZ_I64; + LLVMValueRef width = ins->opcode == OP_X86_BSR32 ? const_int32 (31) : const_int64 (63); + LLVMValueRef tz = call_intrins (ctx, op, args, ""); + values [ins->dreg] = LLVMBuildXor (builder, tz, width, dname); + break; + } #endif case OP_ICONV_TO_I1: diff --git a/mono/mini/mini-mips.c b/mono/mini/mini-mips.c index c816d01596a1..13cf4e578038 100644 --- a/mono/mini/mini-mips.c +++ b/mono/mini/mini-mips.c @@ -5597,3 +5597,10 @@ mono_arch_load_function (MonoJitICallId jit_icall_id) { return NULL; } + +GSList* +mono_arch_get_cie_program (void) +{ + NOT_IMPLEMENTED; + return NULL; +} diff --git a/mono/mini/mini-ops.h b/mono/mini/mini-ops.h index 2e255fc0a383..0af9976d134d 100644 --- a/mono/mini/mini-ops.h +++ b/mono/mini/mini-ops.h @@ -1330,6 +1330,10 @@ MINI_OP(OP_X86_FP_LOAD_I4, "x86_fp_load_i4", FREG, IREG, NONE) MINI_OP(OP_X86_SETEQ_MEMBASE, "x86_seteq_membase", NONE, IREG, NONE) MINI_OP(OP_X86_SETNE_MEMBASE, "x86_setne_membase", NONE, IREG, NONE) MINI_OP(OP_X86_FXCH, "x86_fxch", NONE, NONE, NONE) +MINI_OP(OP_X86_BSF32, "x86_bsf32", IREG, IREG, NONE) +MINI_OP(OP_X86_BSR32, "x86_bsr32", IREG, IREG, NONE) +MINI_OP(OP_X86_BSF64, "x86_bsf64", LREG, LREG, NONE) +MINI_OP(OP_X86_BSR64, "x86_bsr64", LREG, LREG, NONE) #endif #if defined(TARGET_AMD64) diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index caf65d40e88a..c2f4ebbcbab6 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -165,6 +165,7 @@ static GSList *tramp_infos; GSList *mono_interp_only_classes; static void register_icalls (void); +static void runtime_cleanup (MonoDomain *domain, gpointer user_data); gboolean mono_running_on_valgrind (void) @@ -1239,6 +1240,7 @@ mono_patch_info_hash (gconstpointer data) case MONO_PATCH_INFO_SIGNATURE: case MONO_PATCH_INFO_METHOD_CODE_SLOT: case MONO_PATCH_INFO_AOT_JIT_INFO: + case MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE: return hash | (gssize)ji->data.target; case MONO_PATCH_INFO_GSHAREDVT_CALL: return hash | (gssize)ji->data.gsharedvt->method; @@ -1444,6 +1446,10 @@ mono_resolve_patch_target (MonoMethod *method, MonoDomain *domain, guint8 *code, target = code_slot; break; } + case MONO_PATCH_INFO_METHOD_PINVOKE_ADDR_CACHE: { + target = mono_domain_alloc0 (domain, sizeof (gpointer)); + break; + } case MONO_PATCH_INFO_GC_SAFE_POINT_FLAG: target = (gpointer)&mono_polling_required; break; @@ -1965,6 +1971,8 @@ enum { ELF_MACHINE = EM_S390, #elif HOST_RISCV ELF_MACHINE = EM_RISCV, +#elif HOST_MIPS + ELF_MACHINE = EM_MIPS, #endif JIT_CODE_LOAD = 0 }; @@ -4550,6 +4558,7 @@ mini_init (const char *filename, const char *runtime_version) #if defined(ENABLE_PERFTRACING) && !defined(DISABLE_EVENTPIPE) ep_init (); + ep_finish_init (); #endif if (mono_aot_only) { @@ -4613,7 +4622,7 @@ mini_init (const char *filename, const char *runtime_version) #define JIT_RUNTIME_WORKS #ifdef JIT_RUNTIME_WORKS - mono_install_runtime_cleanup ((MonoDomainFunc)mini_cleanup); + mono_install_runtime_cleanup (runtime_cleanup); mono_runtime_init_checked (domain, (MonoThreadStartCB)mono_thread_start_cb, mono_thread_attach_cb, error); mono_error_assert_ok (error); mono_thread_attach (domain); @@ -4987,6 +4996,12 @@ jit_stats_cleanup (void) mono_jit_stats.biggest_method = NULL; } +static void +runtime_cleanup (MonoDomain *domain, gpointer user_data) +{ + mini_cleanup (domain); +} + #ifdef DISABLE_CLEANUP void mini_cleanup (MonoDomain *domain) diff --git a/mono/mini/mini-wasm-debugger.c b/mono/mini/mini-wasm-debugger.c index dc1f5d38f3d2..43e61020b1f4 100644 --- a/mono/mini/mini-wasm-debugger.c +++ b/mono/mini/mini-wasm-debugger.c @@ -24,6 +24,12 @@ static int log_level = 1; #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { fprintf (stdout, __VA_ARGS__); } } while (0) +enum { + EXCEPTION_MODE_NONE, + EXCEPTION_MODE_UNCAUGHT, + EXCEPTION_MODE_ALL +}; + //functions exported to be used by JS G_BEGIN_DECLS @@ -31,22 +37,21 @@ EMSCRIPTEN_KEEPALIVE int mono_wasm_set_breakpoint (const char *assembly_name, in EMSCRIPTEN_KEEPALIVE int mono_wasm_remove_breakpoint (int bp_id); EMSCRIPTEN_KEEPALIVE int mono_wasm_current_bp_id (void); EMSCRIPTEN_KEEPALIVE void mono_wasm_enum_frames (void); -EMSCRIPTEN_KEEPALIVE void mono_wasm_get_var_info (int scope, int* pos, int len); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_local_vars (int scope, int* pos, int len); EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void); EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind); -EMSCRIPTEN_KEEPALIVE void mono_wasm_get_object_properties (int object_id, gboolean expand_value_types); -EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_values (int object_id); -EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_value_expanded (int object_id, int idx); -EMSCRIPTEN_KEEPALIVE void mono_wasm_invoke_getter_on_object (int object_id, const char* name); -EMSCRIPTEN_KEEPALIVE void mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass); +EMSCRIPTEN_KEEPALIVE int mono_wasm_pause_on_exceptions (int state); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, gboolean expand_value_types); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_array_values (int object_id, int start_idx, int count, gboolean expand_value_types); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_object (int object_id, const char* name); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass); //JS functions imported that we use extern void mono_wasm_add_frame (int il_offset, int method_token, const char *assembly_name, const char *method_name); extern void mono_wasm_fire_bp (void); +extern void mono_wasm_fire_exception (int exception_obj_id, const char* message, const char* class_name, gboolean uncaught); extern void mono_wasm_add_obj_var (const char*, const char*, guint64); -extern void mono_wasm_add_value_type_unexpanded_var (const char*, const char*); -extern void mono_wasm_begin_value_type_var (const char*, const char*); -extern void mono_wasm_end_value_type_var (void); extern void mono_wasm_add_enum_var (const char*, const char*, guint64); extern void mono_wasm_add_func_var (const char*, const char*, guint64); extern void mono_wasm_add_properties_var (const char*, gint32); @@ -57,6 +62,7 @@ extern void mono_wasm_add_typed_value (const char *type, const char *str_value, G_END_DECLS static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, gboolean expandValueType); +static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame); //FIXME move all of those fields to the profiler object static gboolean debugger_enabled; @@ -65,6 +71,7 @@ static int event_request_id; static GHashTable *objrefs; static GHashTable *obj_to_objref; static int objref_id = 0; +static int pause_on_exc = EXCEPTION_MODE_NONE; static const char* all_getters_allowed_class_names[] = { @@ -133,7 +140,7 @@ collect_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data) DEBUG_PRINTF (2, "Reporting method %s native_offset %d\n", method->name, info->native_offset); if (!mono_find_prev_seq_point_for_native_offset (mono_get_root_domain (), method, info->native_offset, NULL, &sp)) - DEBUG_PRINTF (2, "Failed to lookup sequence point\n"); + DEBUG_PRINTF (2, "collect_frames: Failed to lookup sequence point. method: %s, native_offset: %d \n", method->name, info->native_offset); DbgEngineStackFrame *frame = g_new0 (DbgEngineStackFrame, 1); @@ -249,7 +256,7 @@ typedef struct { static void* create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind) { - printf ("ss_reqs %d bp_reqs %d\n", ss_reqs->len, bp_reqs->len); + DEBUG_PRINTF (1, "ss_reqs %d bp_reqs %d\n", ss_reqs->len, bp_reqs->len); if ((ss_reqs && ss_reqs->len) || (bp_reqs && bp_reqs->len)) { BpEvents *evts = g_new0 (BpEvents, 1); //just a non-null value to make sure we can raise it on process_breakpoint_events evts->is_ss = (ss_reqs && ss_reqs->len); @@ -276,7 +283,7 @@ no_seq_points_found (MonoMethod *method, int offset) /* * This can happen in full-aot mode with assemblies AOTed without the 'soft-debug' option to save space. */ - printf ("Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method, TRUE), offset); + DEBUG_PRINTF (1, "Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method, TRUE), offset); } #define DBG_NOT_SUSPENDED 1 @@ -284,7 +291,7 @@ no_seq_points_found (MonoMethod *method, int offset) static int ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *ss_args) { - printf ("ss_create_init_args\n"); + DEBUG_PRINTF (1, "ss_create_init_args\n"); int dummy = 0; ss_req->start_sp = ss_req->last_sp = &dummy; compute_frames (); @@ -363,6 +370,8 @@ mono_wasm_debugger_init (void) obj_to_objref = g_hash_table_new (NULL, NULL); objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref); + + mini_get_dbg_callbacks ()->handle_exception = handle_exception; } MONO_API void @@ -373,6 +382,14 @@ mono_wasm_enable_debugging (int debug_level) log_level = debug_level; } +EMSCRIPTEN_KEEPALIVE int +mono_wasm_pause_on_exceptions (int state) +{ + pause_on_exc = state; + DEBUG_PRINTF (1, "setting pause on exception: %d\n", pause_on_exc); + return 1; +} + EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind) { @@ -428,6 +445,50 @@ mono_wasm_setup_single_step (int kind) return isBPOnNativeCode; } +static int +get_object_id(MonoObject *obj) +{ + ObjRef *ref; + if (!obj) + return 0; + + ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj))); + if (ref) + return ref->id; + ref = g_new0 (ObjRef, 1); + ref->id = mono_atomic_inc_i32 (&objref_id); + ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE); + g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref); + g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref); + return ref->id; +} + +static void +handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame) +{ + ERROR_DECL (error); + DEBUG_PRINTF (1, "handle exception - %d - %p - %p - %p\n", pause_on_exc, exc, throw_ctx, catch_ctx); + + if (pause_on_exc == EXCEPTION_MODE_NONE) + return; + if (pause_on_exc == EXCEPTION_MODE_UNCAUGHT && catch_ctx != NULL) + return; + + int obj_id = get_object_id ((MonoObject *)exc); + const char *error_message = mono_string_to_utf8_checked_internal (exc->message, error); + + if (!is_ok (error)) + error_message = "Failed to get exception message."; + + const char *class_name = mono_class_full_name (mono_object_class (exc)); + DEBUG_PRINTF (2, "handle exception - calling mono_wasm_fire_exc(): %d - message - %s, class_name: %s\n", obj_id, error_message, class_name); + + mono_wasm_fire_exception (obj_id, error_message, class_name, !catch_ctx); + + DEBUG_PRINTF (2, "handle exception - done\n"); +} + + EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void) { @@ -567,21 +628,20 @@ mono_wasm_current_bp_id (void) return evt->id; } -static int get_object_id(MonoObject *obj) +static MonoObject* +get_object_from_id (int objectId) { - ObjRef *ref; + ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); + if (!ref) { + DEBUG_PRINTF (2, "get_object_from_id !ref: %d\n", objectId); + return NULL; + } + + MonoObject *obj = mono_gchandle_get_target_internal (ref->handle); if (!obj) - return 0; + DEBUG_PRINTF (2, "get_object_from_id !obj: %d\n", objectId); - ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj))); - if (ref) - return ref->id; - ref = g_new0 (ObjRef, 1); - ref->id = mono_atomic_inc_i32 (&objref_id); - ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE); - g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref); - g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref); - return ref->id; + return obj; } static gboolean @@ -607,7 +667,7 @@ list_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data) DEBUG_PRINTF (2, "Reporting method %s native_offset %d\n", method->name, info->native_offset); if (!mono_find_prev_seq_point_for_native_offset (mono_get_root_domain (), method, info->native_offset, NULL, &sp)) - DEBUG_PRINTF (1, "Failed to lookup sequence point\n"); + DEBUG_PRINTF (2, "list_frames: Failed to lookup sequence point. method: %s, native_offset: %d\n", method->name, info->native_offset); method_full_name = mono_method_full_name (method, FALSE); while (method->is_inflated) @@ -921,17 +981,28 @@ static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandVa mono_wasm_add_enum_var (class_name, enum_members->str, value__); g_string_free (enum_members, TRUE); - } else if (expandValueType) { - char *to_string_val = get_to_string_description (class_name, klass, addr); - mono_wasm_begin_value_type_var (class_name, to_string_val); - g_free (to_string_val); - - // FIXME: isAsyncLocalThis - describe_object_properties_for_klass ((MonoObject*)addr, klass, FALSE, expandValueType); - mono_wasm_end_value_type_var (); } else { char *to_string_val = get_to_string_description (class_name, klass, addr); - mono_wasm_add_value_type_unexpanded_var (class_name, to_string_val); + + if (expandValueType) { + int32_t size = mono_class_value_size (klass, NULL); + void *value_buf = g_malloc0 (size); + mono_value_copy_internal (value_buf, addr, klass); + + EM_ASM ({ + MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2, value_addr: $3, value_size: $4, klass: $5 }); + }, "begin_vt", class_name, to_string_val, value_buf, size, klass); + + g_free (value_buf); + + // FIXME: isAsyncLocalThis + describe_object_properties_for_klass (addr, klass, FALSE, expandValueType); + mono_wasm_add_typed_value ("end_vt", NULL, 0); + } else { + EM_ASM ({ + MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2 }); + }, "unexpanded_vt", class_name, to_string_val); + } g_free (to_string_val); } g_free (class_name); @@ -989,7 +1060,7 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs gboolean is_valuetype; int pnum; char *klass_name; - gboolean getters_allowed; + gboolean auto_invoke_getters; g_assert (klass); is_valuetype = m_class_is_valuetype(klass); @@ -1022,7 +1093,7 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs } klass_name = mono_class_full_name (klass); - getters_allowed = are_getters_allowed (klass_name); + auto_invoke_getters = are_getters_allowed (klass_name); iter = NULL; pnum = 0; @@ -1034,29 +1105,24 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs mono_wasm_add_properties_var (p->name, pnum); sig = mono_method_signature_internal (p->get); - // automatic properties will get skipped - if (!getters_allowed) { + gboolean vt_self_type_getter = is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass; + if (auto_invoke_getters && !vt_self_type_getter) { + invoke_and_describe_getter_value (obj, p); + } else { // not allowed to call the getter here char *ret_class_name = mono_class_full_name (mono_class_from_mono_type_internal (sig->ret)); - // getters not supported for valuetypes, yet - gboolean invokable = !is_valuetype && sig->param_count == 0; + gboolean invokable = sig->param_count == 0; mono_wasm_add_typed_value ("getter", ret_class_name, invokable); g_free (ret_class_name); continue; } - - if (is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass) { - // Property of the same valuetype, avoid endlessly recursion! - mono_wasm_add_typed_value ("getter", klass_name, 0); - continue; - } - - invoke_and_describe_getter_value (obj, p); } pnum ++; } + + g_free (klass_name); } /* @@ -1085,17 +1151,10 @@ static gboolean describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, gboolean expandValueType) { DEBUG_PRINTF (2, "describe_object_properties %llu\n", objectId); - ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); - if (!ref) { - DEBUG_PRINTF (2, "describe_object_properties !ref\n"); - return FALSE; - } - MonoObject *obj = mono_gchandle_get_target_internal (ref->handle); - if (!obj) { - DEBUG_PRINTF (2, "describe_object_properties !obj\n"); + MonoObject *obj = get_object_from_id (objectId); + if (!obj) return FALSE; - } if (m_class_is_delegate (mono_object_class (obj))) { // delegates get the same id format as regular objects @@ -1108,21 +1167,13 @@ describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, gboolea } static gboolean -invoke_getter_on_object (guint64 objectId, const char *name) +invoke_getter (void *obj_or_value, MonoClass *klass, const char *name) { - ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); - if (!ref) { - DEBUG_PRINTF (1, "invoke_getter_on_object no objRef found for id %llu\n", objectId); - return FALSE; - } - - MonoObject *obj = mono_gchandle_get_target_internal (ref->handle); - if (!obj) { - DEBUG_PRINTF (1, "invoke_getter_on_object !obj\n"); + if (!obj_or_value || !klass || !name) { + DEBUG_PRINTF (2, "invoke_getter: none of the arguments can be null"); return FALSE; } - MonoClass *klass = mono_object_class (obj); gpointer iter = NULL; MonoProperty *p; while ((p = mono_class_get_properties (klass, &iter))) { @@ -1130,7 +1181,7 @@ invoke_getter_on_object (guint64 objectId, const char *name) if (!p->get->name || strcasecmp (p->name, name) != 0) continue; - invoke_and_describe_getter_value (obj, p); + invoke_and_describe_getter_value (obj_or_value, p); return TRUE; } @@ -1138,50 +1189,48 @@ invoke_getter_on_object (guint64 objectId, const char *name) } static gboolean -describe_array_values (guint64 objectId) +describe_array_values (guint64 objectId, int startIdx, int count, gboolean expandValueType) { + if (count == 0) + return TRUE; + int esize; gpointer elem; - ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); - if (!ref) { + MonoArray *arr = (MonoArray*) get_object_from_id (objectId); + if (!arr) return FALSE; - } - MonoArray *arr = (MonoArray *)mono_gchandle_get_target_internal (ref->handle); - MonoObject *obj = &arr->obj; - if (!obj) { + + MonoClass *klass = mono_object_class (arr); + MonoTypeEnum type = m_class_get_byval_arg (klass)->type; + if (type != MONO_TYPE_SZARRAY && type != MONO_TYPE_ARRAY) { + DEBUG_PRINTF (1, "describe_array_values: object is not an array. type: 0x%x\n", type); return FALSE; } - esize = mono_array_element_size (obj->vtable->klass); - for (int i = 0; i < arr->max_length; i++) { - mono_wasm_add_array_item(i); - elem = (gpointer*)((char*)arr->vector + (i * esize)); - describe_value (m_class_get_byval_arg (m_class_get_element_class (arr->obj.vtable->klass)), elem, FALSE); + + int len = arr->max_length; + if (len == 0 && startIdx == 0 && count <= 0) { + // Nothing to do + return TRUE; } - return TRUE; -} -/* Expands valuetypes */ -static gboolean -describe_array_value_expanded (guint64 objectId, guint64 idx) -{ - int esize; - gpointer elem; - ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); - if (!ref) { + if (startIdx < 0 || (len > 0 && startIdx >= len)) { + DEBUG_PRINTF (1, "describe_array_values: invalid startIdx (%d) for array of length %d\n", startIdx, len); return FALSE; } - MonoArray *arr = (MonoArray *)mono_gchandle_get_target_internal (ref->handle); - MonoObject *obj = &arr->obj; - if (!obj) { + + if (count > 0 && (startIdx + count) > len) { + DEBUG_PRINTF (1, "describe_array_values: invalid count (%d) for startIdx: %d, and array of length %d\n", count, startIdx, len); return FALSE; } - if (idx >= arr->max_length) - return FALSE; - esize = mono_array_element_size (obj->vtable->klass); - elem = (gpointer*)((char*)arr->vector + (idx * esize)); - describe_value (m_class_get_byval_arg (m_class_get_element_class (arr->obj.vtable->klass)), elem, TRUE); + esize = mono_array_element_size (klass); + int endIdx = count < 0 ? len : startIdx + count; + for (int i = startIdx; i < endIdx; i ++) { + mono_wasm_add_array_item(i); + elem = (gpointer*)((char*)arr->vector + (i * esize)); + describe_value (m_class_get_byval_arg (m_class_get_element_class (klass)), elem, expandValueType); + } return TRUE; } @@ -1284,22 +1333,23 @@ describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointe return TRUE; } -EMSCRIPTEN_KEEPALIVE void +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass) { MonoType *type = m_class_get_byval_arg (klass); if (type->type != MONO_TYPE_PTR && type->type != MONO_TYPE_FNPTR) { DEBUG_PRINTF (2, "BUG: mono_wasm_get_deref_ptr_value: Expected to get a ptr type, but got 0x%x\n", type->type); - return; + return FALSE; } mono_wasm_add_properties_var ("deref", -1); describe_value (type->data.type, value_addr, TRUE); + return TRUE; } //FIXME this doesn't support getting the return value pseudo-var -EMSCRIPTEN_KEEPALIVE void -mono_wasm_get_var_info (int scope, int* pos, int len) +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_get_local_vars (int scope, int* pos, int len) { FrameDescData data; data.target_frame = scope; @@ -1308,37 +1358,51 @@ mono_wasm_get_var_info (int scope, int* pos, int len) data.pos = pos; mono_walk_stack_with_ctx (describe_variables_on_frame, NULL, MONO_UNWIND_NONE, &data); + + return TRUE; } -EMSCRIPTEN_KEEPALIVE void +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, gboolean expand_value_types) { DEBUG_PRINTF (2, "getting properties of object %d\n", object_id); - describe_object_properties (object_id, FALSE, expand_value_types); + return describe_object_properties (object_id, FALSE, expand_value_types); } -EMSCRIPTEN_KEEPALIVE void -mono_wasm_get_array_values (int object_id) +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_get_array_values (int object_id, int start_idx, int count, gboolean expand_value_types) { - DEBUG_PRINTF (2, "getting array values %d\n", object_id); + DEBUG_PRINTF (2, "getting array values %d, startIdx: %d, count: %d, expandValueType: %d\n", object_id, start_idx, count, expand_value_types); - describe_array_values(object_id); + return describe_array_values (object_id, start_idx, count, expand_value_types); } -EMSCRIPTEN_KEEPALIVE void -mono_wasm_get_array_value_expanded (int object_id, int idx) +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_invoke_getter_on_object (int object_id, const char* name) { - DEBUG_PRINTF (2, "getting array value %d for idx %d\n", object_id, idx); + MonoObject *obj = get_object_from_id (object_id); + if (!obj) + return FALSE; - describe_array_value_expanded (object_id, idx); + return invoke_getter (obj, mono_object_class (obj), name); } -EMSCRIPTEN_KEEPALIVE void -mono_wasm_invoke_getter_on_object (int object_id, const char* name) +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name) { - invoke_getter_on_object (object_id, name); + DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: v: %p klass: %p, name: %s\n", value, klass, name); + if (!klass || !value) + return FALSE; + + if (!m_class_is_valuetype (klass)) { + DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: klass is not a valuetype. name: %s\n", mono_class_full_name (klass)); + return FALSE; + } + + return invoke_getter (value, klass, name); } + // Functions required by debugger-state-machine. gsize mono_debugger_tls_thread_id (DebuggerTlsData *debuggerTlsData) diff --git a/mono/mini/mini.h b/mono/mini/mini.h index 58eb136d3181..b242d1223d8a 100644 --- a/mono/mini/mini.h +++ b/mono/mini/mini.h @@ -2261,7 +2261,7 @@ void mini_emit_stobj (MonoCompile *cfg, MonoInst *dest, MonoInst *s void mini_emit_initobj (MonoCompile *cfg, MonoInst *dest, const guchar *ip, MonoClass *klass); void mini_emit_init_rvar (MonoCompile *cfg, int dreg, MonoType *rtype); int mini_emit_sext_index_reg (MonoCompile *cfg, MonoInst *index); -MonoInst* mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, MonoInst *index, gboolean bcheck); +MonoInst* mini_emit_ldelema_1_ins (MonoCompile *cfg, MonoClass *klass, MonoInst *arr, MonoInst *index, gboolean bcheck, gboolean bounded); MonoInst* mini_emit_get_gsharedvt_info_klass (MonoCompile *cfg, MonoClass *klass, MonoRgctxInfoType rgctx_type); MonoInst* mini_emit_get_rgctx_method (MonoCompile *cfg, int context_used, MonoMethod *cmethod, MonoRgctxInfoType rgctx_type); @@ -2719,6 +2719,9 @@ mono_is_partially_sharable_inst (MonoGenericInst *inst); gboolean mini_is_gsharedvt_gparam (MonoType *t); +gboolean +mini_is_gsharedvt_inst (MonoGenericInst *inst); + MonoGenericContext* mini_method_get_context (MonoMethod *method); int mono_method_check_context_used (MonoMethod *method); diff --git a/mono/mini/patch-info.h b/mono/mini/patch-info.h index 3adcde561526..6807f92b5c64 100644 --- a/mono/mini/patch-info.h +++ b/mono/mini/patch-info.h @@ -82,3 +82,6 @@ PATCH_INFO(SPECIFIC_TRAMPOLINES_GOT_SLOTS_BASE, "specific_trampolines_got_slots_ */ PATCH_INFO(R8_GOT, "r8_got") PATCH_INFO(R4_GOT, "r4_got") + +/* MonoMethod* -> the address of a memory slot which is used to cache the pinvoke address */ +PATCH_INFO(METHOD_PINVOKE_ADDR_CACHE, "pinvoke_addr_cache") diff --git a/mono/mini/simd-intrinsics-netcore.c b/mono/mini/simd-intrinsics-netcore.c index a93b334fe4ab..3888ea3bf1d5 100644 --- a/mono/mini/simd-intrinsics-netcore.c +++ b/mono/mini/simd-intrinsics-netcore.c @@ -4,6 +4,7 @@ #include #include +#include #include "mini.h" #if defined(DISABLE_JIT) @@ -27,6 +28,7 @@ mono_simd_intrinsics_init (void) #include "mono/utils/bsearch.h" #include #include +#include #if defined (MONO_ARCH_SIMD_INTRINSICS) && defined(ENABLE_NETCORE) @@ -533,7 +535,7 @@ emit_sys_numerics_vector_t (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSig MONO_EMIT_BOUNDS_CHECK (cfg, array_ins->dreg, MonoArray, max_length, end_index_reg); /* Load the array slice into the simd reg */ - ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE); + ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE, FALSE); g_assert (args [0]->opcode == OP_LDADDR); var = (MonoInst*)args [0]->inst_p0; EMIT_NEW_LOAD_MEMBASE (cfg, ins, OP_LOADX_MEMBASE, var->dreg, ldelema_ins->dreg, 0); @@ -568,7 +570,7 @@ emit_sys_numerics_vector_t (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSig MONO_EMIT_NEW_COND_EXC (cfg, LT, "ArgumentException"); /* Load the array slice into the simd reg */ - ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, FALSE); + ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, FALSE, FALSE); EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STOREX_MEMBASE, ldelema_ins->dreg, 0, val_vreg); ins->klass = cmethod->klass; return ins; @@ -1119,6 +1121,12 @@ static SimdIntrinsic bmi2_methods [] = { {SN_get_IsSupported} }; +static SimdIntrinsic x86base_methods [] = { + {SN_BitScanForward}, + {SN_BitScanReverse}, + {SN_get_IsSupported} +}; + static MonoInst* emit_x86_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) { @@ -1887,6 +1895,39 @@ emit_x86_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature } } + if (is_hw_intrinsics_class (klass, "X86Base", &is_64bit)) { + if (!COMPILE_LLVM (cfg)) + return NULL; + + info = lookup_intrins_info (x86base_methods, sizeof (x86base_methods), cmethod); + if (!info) + return NULL; + int id = info->id; + + switch (id) { + case SN_get_IsSupported: + EMIT_NEW_ICONST (cfg, ins, 1); + ins->type = STACK_I4; + return ins; + case SN_BitScanForward: + MONO_INST_NEW (cfg, ins, is_64bit ? OP_X86_BSF64 : OP_X86_BSF32); + ins->dreg = is_64bit ? alloc_lreg (cfg) : alloc_ireg (cfg); + ins->sreg1 = args [0]->dreg; + ins->type = is_64bit ? STACK_I8 : STACK_I4; + MONO_ADD_INS (cfg->cbb, ins); + return ins; + case SN_BitScanReverse: + MONO_INST_NEW (cfg, ins, is_64bit ? OP_X86_BSR64 : OP_X86_BSR32); + ins->dreg = is_64bit ? alloc_lreg (cfg) : alloc_ireg (cfg); + ins->sreg1 = args [0]->dreg; + ins->type = is_64bit ? STACK_I8 : STACK_I4; + MONO_ADD_INS (cfg->cbb, ins); + return ins; + default: + g_assert_not_reached (); + } + } + return NULL; } @@ -2134,3 +2175,13 @@ MONO_EMPTY_SOURCE_FILE (simd_intrinsics_netcore); #endif #endif /* DISABLE_JIT */ + + +#if defined(ENABLE_NETCORE) && defined(TARGET_AMD64) +void +ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int abcd[4], int function_id, int subfunction_id) +{ + mono_hwcap_x86_call_cpuidex (function_id, subfunction_id, + &abcd [0], &abcd [1], &abcd [2], &abcd [3]); +} +#endif diff --git a/mono/mini/simd-intrinsics.c b/mono/mini/simd-intrinsics.c index 95367010fe36..cd1aa96c40dd 100644 --- a/mono/mini/simd-intrinsics.c +++ b/mono/mini/simd-intrinsics.c @@ -2410,7 +2410,7 @@ emit_vector_t_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSigna MONO_EMIT_BOUNDS_CHECK (cfg, array_ins->dreg, MonoArray, max_length, end_index_reg); /* Load the array slice into the simd reg */ - ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE); + ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE, FALSE); g_assert (args [0]->opcode == OP_LDADDR); var = (MonoInst*)args [0]->inst_p0; EMIT_NEW_LOAD_MEMBASE (cfg, ins, OP_LOADX_MEMBASE, var->dreg, ldelema_ins->dreg, 0); @@ -2554,7 +2554,7 @@ emit_vector_t_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSigna MONO_EMIT_NEW_COND_EXC (cfg, LE_UN, "ArgumentException"); /* Load the simd reg into the array slice */ - ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE); + ldelema_ins = mini_emit_ldelema_1_ins (cfg, mono_class_from_mono_type_internal (etype), array_ins, index_ins, TRUE, FALSE); g_assert (args [0]->opcode == OP_LDADDR); var = (MonoInst*)args [0]->inst_p0; EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STOREX_MEMBASE, ldelema_ins->dreg, 0, var->dreg); diff --git a/mono/mini/simd-methods-netcore.h b/mono/mini/simd-methods-netcore.h index 6ea5b9c4a217..b68dd69eb4fb 100644 --- a/mono/mini/simd-methods-netcore.h +++ b/mono/mini/simd-methods-netcore.h @@ -233,3 +233,6 @@ METHOD(ReverseElementBits) // Crc32 METHOD(ComputeCrc32) METHOD(ComputeCrc32C) +// X86Base +METHOD(BitScanForward) +METHOD(BitScanReverse) diff --git a/mono/profiler/CMakeLists.txt b/mono/profiler/CMakeLists.txt new file mode 100644 index 000000000000..4777a84a757d --- /dev/null +++ b/mono/profiler/CMakeLists.txt @@ -0,0 +1,16 @@ +project(profiler) + +include_directories( + ${PROJECT_BINARY_DIR}/ + ${PROJECT_BINARY_DIR}/../.. + ${PROJECT_BINARY_DIR}/../../mono/eglib + ${CMAKE_SOURCE_DIR}/ + ${PROJECT_SOURCE_DIR}/../ + ${PROJECT_SOURCE_DIR}/../eglib + ${PROJECT_SOURCE_DIR}/../sgen) + +if(NOT DISABLE_LIBS) + add_library(mono-profiler-aot-static STATIC aot.c helper.c) + set_target_properties(mono-profiler-aot-static PROPERTIES OUTPUT_NAME mono-profiler-aot) + install(TARGETS mono-profiler-aot-static LIBRARY) +endif() diff --git a/mono/sgen/sgen-gc.c b/mono/sgen/sgen-gc.c index e6719fae0585..94196085fc6e 100644 --- a/mono/sgen/sgen-gc.c +++ b/mono/sgen/sgen-gc.c @@ -3749,6 +3749,8 @@ sgen_gc_init (void) #endif /* If aot code is used, allocation from there won't expect the layout with canaries enabled */ sgen_set_use_managed_allocator (FALSE); + } else if (!strcmp (opt, "coop-no-stack-scan")) { + sgen_disable_native_stack_scan (); } else if (!sgen_client_handle_gc_debug (opt)) { sgen_env_var_error (MONO_GC_DEBUG_NAME, "Ignoring.", "Unknown option `%s`.", opt); @@ -3776,6 +3778,7 @@ sgen_gc_init (void) fprintf (stderr, " print-allowance\n"); fprintf (stderr, " print-pinning\n"); fprintf (stderr, " print-gchandles\n"); + fprintf (stderr, " coop-no-stack-scan\n"); fprintf (stderr, " heap-dump=\n"); fprintf (stderr, " binary-protocol=[:]\n"); fprintf (stderr, " nursery-canaries\n"); diff --git a/mono/tests/Makefile.am b/mono/tests/Makefile.am index 6a3af788dfe4..8b689e9eb553 100755 --- a/mono/tests/Makefile.am +++ b/mono/tests/Makefile.am @@ -1741,7 +1741,7 @@ LLVM = $(filter --llvm, $(MONO_ENV_OPTIONS) $(AOT_BUILD_FLAGS)) # abort-try-holes.exe is flaky due to unwinding failure to the finally block when aborting # appdomain-marshalbyref-assemblyload.exe: https://bugzilla.xamarin.com/show_bug.cgi?id=49308 # threads-init.exe: runs out of system threads -# dim-constrainedcall.exe: fails on dontnet as well (https://github.com/dotnet/coreclr/issues/15353) +# dim-constrainedcall.exe: fails on dontnet as well (https://github.com/dotnet/runtime/issues/9378) # # tailcall-rgctxb.exe # tailcall-member-function-in-valuetype.exe diff --git a/mono/tests/metadata-verifier/cli-blob-tests.md b/mono/tests/metadata-verifier/cli-blob-tests.md index 89c2e87ce1c7..b93426a47ff8 100644 --- a/mono/tests/metadata-verifier/cli-blob-tests.md +++ b/mono/tests/metadata-verifier/cli-blob-tests.md @@ -20,7 +20,7 @@ method-def-sig { invalid offset blob.i (table-row (6 0) + 10) + 1 set-byte 0x2E invalid offset blob.i (table-row (6 0) + 10) + 1 set-byte 0x2F - #upper nimble flags 0x80 is invalid + #upper nimble flags 0x80 is invalid invalid offset blob.i (table-row (6 0) + 10) + 1 set-bit 7 #sig is too small to decode param count @@ -89,7 +89,7 @@ method-def-ret-misc { method-ref-sig { assembly assembly-with-signatures.exe - #member ref 0 is has a vararg sig + #member ref 0 is has a vararg sig #member ref 1 don't use vararg #2 sentinels @@ -156,23 +156,23 @@ locals-sig { #bad local sig #row 0 has tons of locals - #row 1 is int32&, int32 + #row 1 is int32&, int32 #row 2 is typedref #typedref with byref - #row 1 is: cconv pcount(2) byref int32 int32 + #row 1 is: cconv pcount(2) byref int32 int32 #row 1 goes to: cconv pcount(2) byref typedbyref int32 invalid offset blob.i (table-row (0x11 1)) + 4 set-byte 0x16 #byref pinned int32 - #row 1 is: cconv pcount(2) byref int32 int32 + #row 1 is: cconv pcount(2) byref int32 int32 #row 1 goes to: cconv pcount(1) byref pinned int32 invalid offset blob.i (table-row (0x11 1)) + 2 set-byte 0x01, offset blob.i (table-row (0x11 1)) + 4 set-byte 0x45 #pinned pinned int32 - #row 1 is: cconv pcount(2) byref int32 int32 + #row 1 is: cconv pcount(2) byref int32 int32 #row 1 goes to: cconv pcount(1) pinned pinned int32 #LAMEIMPL MS doesn't care about this valid offset blob.i (table-row (0x11 1)) + 2 set-byte 0x01, @@ -215,7 +215,7 @@ type-enc { invalid offset blob.i (table-row (0x04 3) + 4) + 3 set-byte 0x16 #LAMEIMPL MS verifier doesn't catch this one (runtime does) - #rank 0 + #rank 0 invalid offset blob.i (table-row (0x04 3) + 4) + 4 set-byte 0x00 #large nsizes invalid offset blob.i (table-row (0x04 3) + 4) + 5 set-byte 0x1F @@ -240,7 +240,7 @@ type-enc { #fnptr #field 10 is a fnptr #format is: cconv FNPTR cconv pcount ret param* sentinel? param* - #LAMESPEC, it lacks the fact that fnptr allows for unmanaged call conv + #LAMESPEC, it lacks the fact that fnptr allows for unmanaged call conv #bad callconv invalid offset blob.i (table-row (0x04 10) + 4) + 3 set-byte 0x88 @@ -263,10 +263,10 @@ typespec-sig { #type zero is invalid invalid offset blob.i (table-row (0x1B 0)) + 1 set-byte 0x0 - #LAMESPEC part II, MS allows for cmods on a typespec as well + #LAMESPEC part II, MS allows for cmods on a typespec as well #modreq int32 is invalid #typespec 2 is "modreq int32*" encoded as: PTR CMOD_REQD token INT32 - #change int to CMOD_REQD token INT32 + #change int to CMOD_REQD token INT32 valid offset blob.i (table-row (0x1B 2)) + 1 set-byte 0x1f, #CMOD_REQD offset blob.i (table-row (0x1B 2)) + 2 set-byte read.byte (blob.i (table-row (0x1B 2)) + 3), #token offset blob.i (table-row (0x1B 2)) + 3 set-byte 0x08 #int8 @@ -321,7 +321,7 @@ method-header { #bad fat header flags #only 0x08 and 0x10 allowed - #regular value is + #regular value is invalid offset translate.rva.ind (table-row (0x06 1)) + 0 set-ushort 0x3033 #or 0x20 invalid offset translate.rva.ind (table-row (0x06 1)) + 0 set-ushort 0x3053 invalid offset translate.rva.ind (table-row (0x06 1)) + 0 set-ushort 0x3093 diff --git a/mono/tests/metadata-verifier/cli-cattr-tests.md b/mono/tests/metadata-verifier/cli-cattr-tests.md index ac6849b8a15b..7d852b3a5f80 100644 --- a/mono/tests/metadata-verifier/cli-cattr-tests.md +++ b/mono/tests/metadata-verifier/cli-cattr-tests.md @@ -10,7 +10,7 @@ cattr-without-named-args { #WARNING: peverify don't check custom attributes format beyond the prolog #so it's pointless to use it for this. #We'll take the easy road as well and when verifying the encoded data - #assume that the target constructor can be decoded and use the runtime signature. + #assume that the target constructor can be decoded and use the runtime signature. #bad size invalid offset blob.i (table-row (0x0C 0) + 4) + 0 set-byte 0x0 diff --git a/mono/tests/metadata-verifier/cli-global-props-tests.md b/mono/tests/metadata-verifier/cli-global-props-tests.md index ccbdca2e319b..481113390c38 100644 --- a/mono/tests/metadata-verifier/cli-global-props-tests.md +++ b/mono/tests/metadata-verifier/cli-global-props-tests.md @@ -24,4 +24,4 @@ fielddef-global-props { badrt offset table-row (4 1) + 2 set-ushort read.ushort (table-row (4 0) + 2), #name offset table-row (4 1) + 4 set-ushort read.ushort (table-row (4 0) + 4) #signature -} \ No newline at end of file +} diff --git a/mono/tests/metadata-verifier/cli-metadata-tests.md b/mono/tests/metadata-verifier/cli-metadata-tests.md index 1ce3b3e187a9..fd04a9719566 100644 --- a/mono/tests/metadata-verifier/cli-metadata-tests.md +++ b/mono/tests/metadata-verifier/cli-metadata-tests.md @@ -54,6 +54,6 @@ cli-metadata-stream-headers { #unkwnown name invalid offset stream-header ( 0 ) + 8 set-byte 0x42 - #duplicate name, change #~ to #US + #duplicate name, change #~ to #US invalid offset stream-header ( 0 ) + 9 set-byte 0x55 , offset stream-header ( 0 ) + 10 set-byte 0x53 -} \ No newline at end of file +} diff --git a/mono/tests/metadata-verifier/cli-tables-tests.md b/mono/tests/metadata-verifier/cli-tables-tests.md index cec99ac9f9d0..5412ec803ef0 100644 --- a/mono/tests/metadata-verifier/cli-tables-tests.md +++ b/mono/tests/metadata-verifier/cli-tables-tests.md @@ -5,7 +5,7 @@ tables-header { valid offset cli-metadata + read.uint ( stream-header ( 0 ) ) + 4 set-byte 2 valid offset tables-header + 4 set-byte 2 - #major/minor versions + #major/minor versions invalid offset tables-header + 4 set-byte 22 invalid offset tables-header + 5 set-byte 1 @@ -67,7 +67,7 @@ module-table { valid offset tables-header + 24 set-uint 1 invalid offset tables-header + 24 set-uint 0 invalid offset tables-header + 24 set-uint 2 , offset tables-header + 32 set-uint 1 - + #name #invalid string invalid offset table-row ( 0 0 ) + 2 set-ushort 0x8888 @@ -120,7 +120,7 @@ typedef-table { valid offset tables-header + 32 set-uint 2 invalid offset tables-header + 32 set-uint 0 - #This part of the test suite only verifies structural properties, not table relationships + #This part of the test suite only verifies structural properties, not table relationships #Flags invalid bits: 6,9,14,15,19,21,24-31 invalid offset table-row ( 2 1 ) set-bit 6 @@ -186,7 +186,7 @@ typedef-table-field-list { valid offset table-row ( 2 1 ) + 10 set-ushort 1 - #bad field list + #bad field list invalid offset table-row ( 2 1 ) + 10 set-ushort 999 #this type is bigger than the next @@ -202,7 +202,7 @@ typedef-table-method-list { valid offset table-row ( 2 1 ) + 12 set-ushort 1 - #bad field list + #bad field list invalid offset table-row ( 2 1 ) + 12 set-ushort 999 #this type is bigger than the next @@ -262,11 +262,11 @@ field-table { #if it's a global variable, it must be static and (public|compiler controler|private) (16) #static + compiler controled - valid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x10 + valid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x10 #static + private valid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x11 #static + public - valid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x16 + valid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x16 #static + bad visibility #LAMEIMPL MS doesn't verify visibility invalid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x12 @@ -275,7 +275,7 @@ field-table { invalid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x15 #public and not static - invalid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x06 + invalid offset table-row ( 2 1 ) + 10 set-ushort 2 , offset table-row ( 4 0 ) set-ushort 0x06 #field is constant but has no row in the contant table #LAMESPEC this check is missing from the spec @@ -300,7 +300,7 @@ methoddef-table { #bad flags (4) #no unused bits - + #invalid .ctor with generic params and specialname (6) #method 0 is a .ctor, method 1 is generic invalid offset table-row ( 6 1 ) + 6 or-ushort 0x1800 , offset table-row ( 6 1 ) + 8 set-ushort read.ushort ( table-row ( 6 0 ) + 8 ) @@ -343,8 +343,8 @@ methoddef-table { #Interface cannot have .ctors (15) #method 3 belongs to an inteface invalid offset table-row ( 6 3 ) + 8 set-ushort read.ushort ( table-row ( 6 0 ) + 8 ) - #Interface methods can't be static - invalid offset table-row ( 6 3 ) + 6 or-ushort 0x0010 + #Interface methods can't be static + invalid offset table-row ( 6 3 ) + 6 or-ushort 0x0010 #XXX we don't care about CLS names (17) @@ -377,7 +377,7 @@ methoddef-table { invalid offset table-row ( 6 5 ) set-uint read.uint ( table-row ( 6 2 ) ) #pinvoke with runtime - #LAMEIMPL/SPEC either MS ignores it or the spec is ill defined + #LAMEIMPL/SPEC either MS ignores it or the spec is ill defined #invalid offset table-row ( 6 5 ) + 4 or-ushort 0x1000 #if compilercontroled (0x0) it must have an RVA or a pinvoke @@ -389,7 +389,7 @@ methoddef-table { #if RVA = 0 then one of (abstract, runtime, pinvoke) (34) #let's test with an abstract class, method 6 is abstract and belongs to one. invalid offset table-row ( 6 7 ) + 6 set-ushort 0x0006 - #icall + #icall valid offset table-row ( 6 7 ) + 6 set-ushort 0x01c6 , offset table-row ( 6 7 ) + 4 or-ushort 0x1000 #if rva != 0 then abstract == 0 and codetypemask must be (native,cil,runtime) and rva shall be valid (35) @@ -429,7 +429,7 @@ methoddef-table-global-methods { assembly assembly-with-global-method.exe #checks for methods owned by (20) - + #static + public valid offset table-row ( 6 0 ) + 6 set-ushort 0x0010 #static + private @@ -458,10 +458,10 @@ methoddef-table-global-methods { methoddef-table-params { assembly assembly-with-methods.exe - #method 12,13,14 have 3 params and params: 2,5,8 + #method 12,13,14 have 3 params and params: 2,5,8 #method 13 has 3 params and params: 5 invalid offset table-row ( 6 12 ) + 12 set-ushort 6 - invalid offset table-row ( 6 13 ) + 12 set-ushort 99 + invalid offset table-row ( 6 13 ) + 12 set-ushort 99 } @@ -489,7 +489,7 @@ param-table { invalid offset table-row ( 8 0 ) + 2 set-ushort 2 invalid offset table-row ( 8 1 ) + 2 set-ushort 1 - + #if HasDefault = 1 then there must be a row in the constant table (6) #param 2 doesn't have a default invalid offset table-row ( 8 2 ) or-ushort 0x1000 @@ -534,7 +534,7 @@ interfaceimpl-table { memberref-table { assembly assembly-with-complex-type.exe - + #class must be a valid token (1 2) #null invalid offset table-row ( 10 0 ) set-ushort 0 @@ -555,13 +555,13 @@ memberref-table { #signature must be valid (5) invalid offset table-row ( 10 0 ) + 4 set-ushort 0x9900 - + #TODO validate the signature (5) #LAMESPEC CompilerControled visibility (9,10) is nice but no impl care about - #LAMESPEC what does (11) mean? + #LAMESPEC what does (11) mean? } constant-table { @@ -588,7 +588,7 @@ constant-table { #First remove default from param 'a' (param table idx 0) #Then set the has default flag in the property table #Finally, make the first constant point from the part to the property (const 1, prop 0, token 0x6) - valid offset table-row ( 0x8 0 ) set-ushort 0 , offset table-row ( 0x17 0 ) or-ushort 0x1000 , offset table-row ( 0xB 1 ) + 2 set-ushort 0x6 + valid offset table-row ( 0x8 0 ) set-ushort 0 , offset table-row ( 0x17 0 ) or-ushort 0x1000 , offset table-row ( 0xB 1 ) + 2 set-ushort 0x6 #Invalid coded table invalid offset table-row ( 0xB 0 ) + 2 set-ushort 0x0013 , offset table-row ( 0x04 0 ) set-ushort 0x16 @@ -608,7 +608,7 @@ constant-table { invalid offset table-row ( 0xB 0 ) + 4 set-ushort read.uint ( stream-header ( 3 ) + 4 ) #LAMEIMPL, MS doesn't bound check the constant size. Lame of them. - invalid offset table-row ( 0xB 0 ) + 4 set-ushort read.uint ( stream-header ( 3 ) + 4 ) - 1 + invalid offset table-row ( 0xB 0 ) + 4 set-ushort read.uint ( stream-header ( 3 ) + 4 ) - 1 } cattr-table { @@ -708,7 +708,7 @@ class-layout-table { #packing must be (0,1,2,4,8,16,32,64,128) (4) invalid offset table-row ( 0xF 0 ) set-ushort 0x0003 - #TODO do checks depending on the kind of parent (4) + #TODO do checks depending on the kind of parent (4) #Check layout along the inheritance chain. (7) } @@ -753,7 +753,7 @@ event-table { assembly assembly-with-events.exe #event flags have valid bits (3) - #only bits 9 and 10 are used + #only bits 9 and 10 are used invalid offset table-row ( 0x14 0 ) set-bit 0 invalid offset table-row ( 0x14 0 ) set-bit 1 @@ -816,7 +816,7 @@ property-table { assembly assembly-with-properties.exe #valid flags (3) - #only bits 9, 10 and 12 are used + #only bits 9, 10 and 12 are used invalid offset table-row ( 0x17 0 ) set-bit 0 invalid offset table-row ( 0x17 0 ) set-bit 1 invalid offset table-row ( 0x17 0 ) set-bit 2 @@ -846,7 +846,7 @@ property-table { #field zero has default value valid offset table-row (0x17 0) + 0 or-ushort 0x1000, #mark the property with hasdefault offset table-row (0x04 0) + 0 set-ushort 0x0011, #clear literal and hasdefault from the field - offset table-row (0x0B 0) + 2 set-ushort 0x0006 #change the parent token to row 1 of the property table (0x2) + offset table-row (0x0B 0) + 2 set-ushort 0x0006 #change the parent token to row 1 of the property table (0x2) invalid offset table-row (0x17 0) + 0 or-ushort 0x1000 @@ -857,32 +857,32 @@ methodimpl-table { assembly assembly-with-complex-type.exe #class shall be valid (2) - invalid offset table-row (0x19 0) set-ushort 0 + invalid offset table-row (0x19 0) set-ushort 0 invalid offset table-row (0x19 0) set-ushort 0x8800 #methodbody shall be valid (3) #null - invalid offset table-row (0x19 0) + 2 set-ushort 0x0000 + invalid offset table-row (0x19 0) + 2 set-ushort 0x0000 invalid offset table-row (0x19 0) + 2 set-ushort 0x0001 #out of range - invalid offset table-row (0x19 0) + 2 set-ushort 0x8800 + invalid offset table-row (0x19 0) + 2 set-ushort 0x8800 invalid offset table-row (0x19 0) + 2 set-ushort 0x8801 #MethodDeclaration shall be valid #null - invalid offset table-row (0x19 0) + 4 set-ushort 0x0000 + invalid offset table-row (0x19 0) + 4 set-ushort 0x0000 invalid offset table-row (0x19 0) + 4 set-ushort 0x0001 #out of range - invalid offset table-row (0x19 0) + 4 set-ushort 0x8800 + invalid offset table-row (0x19 0) + 4 set-ushort 0x8800 invalid offset table-row (0x19 0) + 4 set-ushort 0x8801 - - #TODO check MethodDeclaration method for virtual and owner type for !sealed (4,5) + + #TODO check MethodDeclaration method for virtual and owner type for !sealed (4,5) #TODO check MethodBody for belonging to a super type of Class,been virtual and rva != 0 (6,7,8) #TODO check MethodBody must belong to any ancestor or iface of Class (9) #TODO check MethodDeclaration method shall not be final (10) #TODO if MethodDeclaration is strict, it must be visible to Class (11) - #TODO the method signature of MethodBody must match of MethodDeclaration (12) + #TODO the method signature of MethodBody must match of MethodDeclaration (12) #TODO no dups } @@ -959,7 +959,7 @@ fieldrva-table { invalid offset table-row (0x1D 0) + 4 set-ushort 0, offset table-row (0x04 17) set-ushort 0x0013 #remove fieldrva from target field invalid offset table-row (0x1D 0) + 4 set-ushort 0x9901, - offset table-row (0x04 17) set-ushort 0x0013 + offset table-row (0x04 17) set-ushort 0x0013 #TODO verify if the field is a blitable valuetype @@ -974,7 +974,7 @@ assembly-table { invalid offset tables-header + 40 set-uint 2, offset stream-header (0) + 4 set-uint read.uint (stream-header (0) + 4) + 22 #increase the size of the #~ section - #bad hasalg (2) + #bad hasalg (2) valid offset table-row (0x20 0) set-uint 0 valid offset table-row (0x20 0) set-uint 0x8003 valid offset table-row (0x20 0) set-uint 0x8004 @@ -997,7 +997,7 @@ assembly-table { #valid pub key (5) - valid offset table-row (0x20 0) + 16 set-ushort 0 + valid offset table-row (0x20 0) + 16 set-ushort 0 invalid offset table-row (0x20 0) + 16 set-ushort 0x9990 #name is a valid non-empty string (5) @@ -1005,7 +1005,7 @@ assembly-table { invalid offset table-row (0x20 0) + 18 set-ushort 0x9990 #culture is an optional valid non-empty string (8) - valid offset table-row (0x20 0) + 20 set-ushort 0 + valid offset table-row (0x20 0) + 20 set-ushort 0 invalid offset table-row (0x20 0) + 20 set-ushort 0x9990 #TODO check if culture is one of the listed cultures (9) (23.1.3) @@ -1036,7 +1036,7 @@ assembly-ref-table { invalid offset table-row (0x23 0) + 14 set-ushort 0 #culture is an optional valid non-empty string (6) - valid offset table-row (0x23 0) + 16 set-ushort 0 + valid offset table-row (0x23 0) + 16 set-ushort 0 invalid offset table-row (0x23 0) + 16 set-ushort 0x9990 #TODO check if culture is one of the listed cultures (7) (23.1.3) @@ -1104,11 +1104,11 @@ exported-type-table { #if Implementation points to exported type table visibility must be nested public (5) #invalid offset table-row (0x27 1) set-uint 0x100005 #LAMEIMPL/SPEC this check is not really relevant - + #typename is a valid non-empty string (7) invalid offset table-row (0x27 0) + 8 set-ushort 0 invalid offset table-row (0x27 0) + 8 set-ushort 0x9900 - + #typenamedpace is a valid string (8,9) invalid offset table-row (0x27 0) + 10 set-ushort 0x9900 @@ -1151,13 +1151,13 @@ manifest-resource-table { valid offset table-row (0x28 0) + 10 set-ushort 0, offset table-row (0x28 0) + 0 set-uint 1 - #LAMEIMPL it doesn't check the resource offset! + #LAMEIMPL it doesn't check the resource offset! invalid offset table-row (0x28 0) + 10 set-ushort 0, offset table-row (0x28 0) + 0 set-uint 0x990000 - + #implementation is a valid token (8) - #does it accept exported type? + #does it accept exported type? invalid offset table-row (0x28 0) + 10 set-ushort 0x0006 #coded table 4 is invalid @@ -1169,7 +1169,7 @@ manifest-resource-table { #if implementation point to a file it's index must be zero (10) #row 0 is a file resource invalid offset table-row (0x28 0) set-uint 1 - + #TODO check for dups (9) } @@ -1185,7 +1185,7 @@ nested-class-table { invalid offset table-row (0x29 0) + 2 set-ushort read.ushort (table-row (0x29 0)) - #TODO check for dups based on nestedclass (5) + #TODO check for dups based on nestedclass (5) } @@ -1214,7 +1214,7 @@ generic-param-table { invalid offset table-row (0x2A 0) + 4 set-ushort 0x8800 invalid offset table-row (0x2A 0) + 4 set-ushort 0x8801 - #bad or empty name + #bad or empty name invalid offset table-row (0x2A 0) + 6 set-ushort 0 invalid offset table-row (0x2A 0) + 6 set-ushort 0x8800 diff --git a/mono/tests/metadata-verifier/data-directory-tests.md b/mono/tests/metadata-verifier/data-directory-tests.md index 03be4f38c5fc..d58e567e9b93 100644 --- a/mono/tests/metadata-verifier/data-directory-tests.md +++ b/mono/tests/metadata-verifier/data-directory-tests.md @@ -3,18 +3,18 @@ pe-data-directories-export-table { assembly simple-assembly.exe #zero is fine - valid offset pe-optional-header + 96 set-uint 0 + valid offset pe-optional-header + 96 set-uint 0 valid offset pe-optional-header + 100 set-uint 0 #RVA must be zero - invalid offset pe-optional-header + 96 set-uint 0x2000 , offset pe-optional-header + 100 set-uint 10 + invalid offset pe-optional-header + 96 set-uint 0x2000 , offset pe-optional-header + 100 set-uint 10 } pe-data-directories-import-table { #Simple assembly has 2 sections since it doesn't have any resources assembly simple-assembly.exe - + #The IT is 40 bytes long invalid offset pe-optional-header + 108 set-uint 0 invalid offset pe-optional-header + 108 set-uint 8 @@ -34,35 +34,35 @@ pe-data-directories-bad-tables { #export invalid offset pe-optional-header + 96 set-uint 0x2000 - #exception - invalid offset pe-optional-header + 120 set-uint 0x2000 + #exception + invalid offset pe-optional-header + 120 set-uint 0x2000 #certificate some assemblies have it. - #invalid offset pe-optional-header + 128 set-uint 0x2000 + #invalid offset pe-optional-header + 128 set-uint 0x2000 - #debug MS uses it for putting debug info in the assembly - #invalid offset pe-optional-header + 144 set-uint 0x2000 + #debug MS uses it for putting debug info in the assembly + #invalid offset pe-optional-header + 144 set-uint 0x2000 - #copyright - invalid offset pe-optional-header + 152 set-uint 0x2000 + #copyright + invalid offset pe-optional-header + 152 set-uint 0x2000 - #global ptr - invalid offset pe-optional-header + 160 set-uint 0x2000 + #global ptr + invalid offset pe-optional-header + 160 set-uint 0x2000 - #tls table - invalid offset pe-optional-header + 168 set-uint 0x2000 + #tls table + invalid offset pe-optional-header + 168 set-uint 0x2000 - #load config - invalid offset pe-optional-header + 176 set-uint 0x2000 + #load config + invalid offset pe-optional-header + 176 set-uint 0x2000 - #bound import - invalid offset pe-optional-header + 184 set-uint 0x2000 + #bound import + invalid offset pe-optional-header + 184 set-uint 0x2000 #delay import - invalid offset pe-optional-header + 200 set-uint 0x2000 + invalid offset pe-optional-header + 200 set-uint 0x2000 #reserved import - invalid offset pe-optional-header + 216 set-uint 0x2000 + invalid offset pe-optional-header + 216 set-uint 0x2000 } diff --git a/mono/tests/metadata-verifier/gen-md-tests.c b/mono/tests/metadata-verifier/gen-md-tests.c index 374a2cdd4dc1..b59bc86db617 100644 --- a/mono/tests/metadata-verifier/gen-md-tests.c +++ b/mono/tests/metadata-verifier/gen-md-tests.c @@ -272,7 +272,7 @@ init_test_set (test_set_t *test_set) if (test_set->init) return; test_set->assembly_data = read_whole_file_and_close (test_set->assembly, &test_set->assembly_size); - test_set->image = mono_image_open_from_data_internal (mono_domain_default_alc (mono_root_domain_get ()), test_set->assembly_data, test_set->assembly_size, FALSE, &status, FALSE, FALSE, NULL); + test_set->image = mono_image_open_from_data_internal (mono_domain_default_alc (mono_root_domain_get ()), test_set->assembly_data, test_set->assembly_size, FALSE, &status, FALSE, FALSE, NULL, NULL); if (!test_set->image || status != MONO_IMAGE_OK) { printf ("Could not parse image %s\n", test_set->assembly); exit (INVALID_BAD_FILE); diff --git a/mono/tests/metadata-verifier/header-tests.md b/mono/tests/metadata-verifier/header-tests.md index 4534ba2299da..4ce8616a6a74 100644 --- a/mono/tests/metadata-verifier/header-tests.md +++ b/mono/tests/metadata-verifier/header-tests.md @@ -24,21 +24,21 @@ msdos-lfanew { invalid offset 0x3f truncate #not enough space for the PE water mark - invalid offset 0x3c set-uint 0xffffffff - invalid offset 0x3c set-uint file-size - 1 + invalid offset 0x3c set-uint 0xffffffff + invalid offset 0x3c set-uint file-size - 1 invalid offset 0x3c set-uint file-size - 2 } pe-signature { assembly simple-assembly.exe - valid offset pe-signature + 0 set-byte 'P' - valid offset pe-signature + 1 set-byte 'E' + valid offset pe-signature + 0 set-byte 'P' + valid offset pe-signature + 1 set-byte 'E' valid offset pe-signature + 2 set-byte 0 valid offset pe-signature + 3 set-byte 0 - invalid offset pe-signature + 0 set-byte 'M' - invalid offset pe-signature + 1 set-byte 'K' + invalid offset pe-signature + 0 set-byte 'M' + invalid offset pe-signature + 1 set-byte 'K' invalid offset pe-signature + 2 set-byte 1 invalid offset pe-signature + 3 set-byte 2 @@ -100,7 +100,7 @@ pe-optional-header-standard-fields { valid offset pe-optional-header + 3 set-byte 0 valid offset pe-optional-header + 3 set-byte 99 - + #Code size is just an informative field as well, nobody cares valid offset pe-optional-header + 4 set-uint 0 valid offset pe-optional-header + 4 set-uint 0x999999 diff --git a/mono/tests/metadata-verifier/resources-tests.md b/mono/tests/metadata-verifier/resources-tests.md index 2443d1f5d06a..06f5d1e09403 100644 --- a/mono/tests/metadata-verifier/resources-tests.md +++ b/mono/tests/metadata-verifier/resources-tests.md @@ -15,4 +15,4 @@ resources-master-directory { invalid offset translate.rva.ind ( pe-optional-header + 112 ) + 14 set-ushort 0x9999 #I won't check anything more than that for now as this is only used by out asp.net stack. -} \ No newline at end of file +} diff --git a/mono/tests/metadata-verifier/section-table-tests.md b/mono/tests/metadata-verifier/section-table-tests.md index 07f85ecda0e4..8bb851119320 100644 --- a/mono/tests/metadata-verifier/section-table-tests.md +++ b/mono/tests/metadata-verifier/section-table-tests.md @@ -25,7 +25,7 @@ pe-section-headers { #VirtualSize = file size + PointerToRawData + 32 invalid offset section-table + 16 set-uint file-size - read.uint ( section-table + 20 ) + 32 invalid offset section-table + 56 set-uint file-size - read.uint ( section-table + 60 ) + 32 - + invalid offset section-table + 60 set-uint 90000 #FIXME add section relocation tests @@ -35,12 +35,12 @@ pe-section-header-flags { #Simple assembly has 2 sections since it doesn't have any resources assembly simple-assembly.exe - #first section is always text + #first section is always text valid offset section-table + 36 set-uint 0x60000020 valid offset section-table + 76 set-uint 0x42000040 - + invalid offset section-table + 36 set-uint 0 invalid offset section-table + 36 set-uint 0xFFFFFFFF -} \ No newline at end of file +} diff --git a/mono/tools/offsets-tool/offsets-tool.py b/mono/tools/offsets-tool/offsets-tool.py index f9f7170aac31..e7c2021694cb 100644 --- a/mono/tools/offsets-tool/offsets-tool.py +++ b/mono/tools/offsets-tool/offsets-tool.py @@ -130,6 +130,11 @@ def require_emscipten_path (args): self.target = Target ("TARGET_X86", "", IOS_DEFINES) self.target_args += ["-arch", "i386"] self.target_args += ["-isysroot", args.sysroot] + elif "x86_64-apple-darwin10" == args.abi: + require_sysroot (args) + self.target = Target ("TARGET_AMD64", "", IOS_DEFINES) + self.target_args += ["-arch", "x86_64"] + self.target_args += ["-isysroot", args.sysroot] # watchOS elif "armv7k-apple-darwin" == args.abi: diff --git a/mono/utils/mono-dl-posix.c b/mono/utils/mono-dl-posix.c index a6c767c8285d..7b19cd5174af 100644 --- a/mono/utils/mono-dl-posix.c +++ b/mono/utils/mono-dl-posix.c @@ -130,21 +130,21 @@ mono_dl_lookup_symbol (MonoDl *module, const char *name) } int -mono_dl_convert_flags (int flags) +mono_dl_convert_flags (int mono_flags, int native_flags) { - int lflags = 0; + int lflags = native_flags; #ifdef ENABLE_NETCORE // Specifying both will default to LOCAL - if (flags & MONO_DL_GLOBAL && !(flags & MONO_DL_LOCAL)) + if (mono_flags & MONO_DL_GLOBAL && !(mono_flags & MONO_DL_LOCAL)) lflags |= RTLD_GLOBAL; else lflags |= RTLD_LOCAL; #else - lflags = flags & MONO_DL_LOCAL ? RTLD_LOCAL : RTLD_GLOBAL; + lflags = mono_flags & MONO_DL_LOCAL ? RTLD_LOCAL : RTLD_GLOBAL; #endif - if (flags & MONO_DL_LAZY) + if (mono_flags & MONO_DL_LAZY) lflags |= RTLD_LAZY; else lflags |= RTLD_NOW; diff --git a/mono/utils/mono-dl-wasm.c b/mono/utils/mono-dl-wasm.c index cd1ce22495e9..333d54db7991 100644 --- a/mono/utils/mono-dl-wasm.c +++ b/mono/utils/mono-dl-wasm.c @@ -1,4 +1,5 @@ #include +#include #if defined (HOST_WASM) @@ -55,23 +56,23 @@ mono_dl_current_error_string (void) return g_strdup (""); } - +// Copied from mono-dl-posix.c int -mono_dl_convert_flags (int flags) +mono_dl_convert_flags (int mono_flags, int native_flags) { - int lflags = 0; + int lflags = native_flags; #ifdef ENABLE_NETCORE // Specifying both will default to LOCAL - if (flags & MONO_DL_LOCAL) - lflags |= RTLD_LOCAL; - else if (flags & MONO_DL_GLOBAL) + if (mono_flags & MONO_DL_GLOBAL && !(mono_flags & MONO_DL_LOCAL)) lflags |= RTLD_GLOBAL; + else + lflags |= RTLD_LOCAL; #else - lflags = flags & MONO_DL_LOCAL ? RTLD_LOCAL : RTLD_GLOBAL; + lflags = mono_flags & MONO_DL_LOCAL ? RTLD_LOCAL : RTLD_GLOBAL; #endif - if (flags & MONO_DL_LAZY) + if (mono_flags & MONO_DL_LAZY) lflags |= RTLD_LAZY; else lflags |= RTLD_NOW; @@ -91,4 +92,8 @@ mono_dl_close_handle (MonoDl *module) { } +#else + +MONO_EMPTY_SOURCE_FILE (mono_dl_wasm); + #endif diff --git a/mono/utils/mono-dl-windows.c b/mono/utils/mono-dl-windows.c index 88c4911ee2b6..966d71a6b00c 100644 --- a/mono/utils/mono-dl-windows.c +++ b/mono/utils/mono-dl-windows.c @@ -57,7 +57,7 @@ mono_dl_open_file (const char *file, int flags) guint32 last_error = 0; #if HAVE_API_SUPPORT_WIN32_LOAD_LIBRARY - hModule = LoadLibraryW (file_utf16); + hModule = LoadLibraryExW (file_utf16, NULL, flags); #elif HAVE_API_SUPPORT_WIN32_LOAD_PACKAGED_LIBRARY hModule = LoadPackagedLibrary (file_utf16, NULL); #else @@ -160,9 +160,10 @@ mono_dl_lookup_symbol (MonoDl *module, const char *symbol_name) } int -mono_dl_convert_flags (int flags) +mono_dl_convert_flags (int mono_flags, int native_flags) { - return 0; + // Mono flags are not applicable on Windows + return native_flags; } #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) diff --git a/mono/utils/mono-dl.c b/mono/utils/mono-dl.c index 32fc1daed7db..ab4712778e26 100644 --- a/mono/utils/mono-dl.c +++ b/mono/utils/mono-dl.c @@ -220,11 +220,17 @@ mono_dl_open_self (char **error_msg) */ MonoDl* mono_dl_open (const char *name, int flags, char **error_msg) +{ + return mono_dl_open_full (name, flags, 0, error_msg); +} + +MonoDl * +mono_dl_open_full (const char *name, int mono_flags, int native_flags, char **error_msg) { MonoDl *module; void *lib; MonoDlFallbackHandler *dl_fallback = NULL; - int lflags = mono_dl_convert_flags (flags); + int lflags = mono_dl_convert_flags (mono_flags, native_flags); char *found_name; if (error_msg) diff --git a/mono/utils/mono-dl.h b/mono/utils/mono-dl.h index 6efee6feabfd..0f6132e150f3 100644 --- a/mono/utils/mono-dl.h +++ b/mono/utils/mono-dl.h @@ -43,7 +43,11 @@ char* mono_dl_build_path (const char *directory, const char *name, void ** MonoDl* mono_dl_open_runtime_lib (const char *lib_name, int flags, char **error_msg); -MonoDl* mono_dl_open_self (char **error_msg); +MonoDl * +mono_dl_open_self (char **error_msg); +// This converts the MONO_DL_* enum to native flags, combines it with the other flags passed, and resolves some inconsistencies +MonoDl * +mono_dl_open_full (const char *name, int mono_flags, int native_flags, char **error_msg); //Platform API for mono_dl @@ -52,7 +56,7 @@ const char** mono_dl_get_so_suffixes (void); void* mono_dl_open_file (const char *file, int flags); void mono_dl_close_handle (MonoDl *module); void* mono_dl_lookup_symbol (MonoDl *module, const char *name); -int mono_dl_convert_flags (int flags); +int mono_dl_convert_flags (int mono_flags, int native_flags); char* mono_dl_current_error_string (void); int mono_dl_get_executable_path (char *buf, int buflen); const char* mono_dl_get_system_dir (void); diff --git a/mono/utils/mono-hwcap-vars.h b/mono/utils/mono-hwcap-vars.h index b408568c729f..2c60a6441394 100644 --- a/mono/utils/mono-hwcap-vars.h +++ b/mono/utils/mono-hwcap-vars.h @@ -81,4 +81,7 @@ MONO_HWCAP_VAR(x86_has_lzcnt) MONO_HWCAP_VAR(x86_has_popcnt) MONO_HWCAP_VAR(x86_has_avx) +gboolean +mono_hwcap_x86_call_cpuidex (int id, int sub_id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx); + #endif diff --git a/mono/utils/mono-hwcap-x86.c b/mono/utils/mono-hwcap-x86.c index 7bbaa2a88a8e..40bf6d37fe78 100644 --- a/mono/utils/mono-hwcap-x86.c +++ b/mono/utils/mono-hwcap-x86.c @@ -29,8 +29,8 @@ #include #endif -static gboolean -cpuid (int id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx) +gboolean +mono_hwcap_x86_call_cpuidex (int id, int sub_id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx) { #if defined(_MSC_VER) int info [4]; @@ -80,7 +80,7 @@ cpuid (int id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx) /* Now issue the actual cpuid instruction. We can use MSVC's __cpuid on both 32-bit and 64-bit. */ #if defined(_MSC_VER) - __cpuid (info, id); + __cpuidex (info, id, sub_id); *p_eax = info [0]; *p_ebx = info [1]; *p_ecx = info [2]; @@ -93,13 +93,13 @@ cpuid (int id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx) "cpuid\n\t" "xchgl\t%%ebx, %k1\n\t" : "=a" (*p_eax), "=&r" (*p_ebx), "=c" (*p_ecx), "=d" (*p_edx) - : "0" (id) + : "0" (id), "2" (sub_id) ); #else __asm__ __volatile__ ( "cpuid\n\t" : "=a" (*p_eax), "=b" (*p_ebx), "=c" (*p_ecx), "=d" (*p_edx) - : "a" (id) + : "a" (id), "2" (sub_id) ); #endif @@ -111,7 +111,7 @@ mono_hwcap_arch_init (void) { int eax, ebx, ecx, edx; - if (cpuid (1, &eax, &ebx, &ecx, &edx)) { + if (mono_hwcap_x86_call_cpuidex (1, 0, &eax, &ebx, &ecx, &edx)) { if (edx & (1 << 15)) { mono_hwcap_x86_has_cmov = TRUE; @@ -144,16 +144,16 @@ mono_hwcap_arch_init (void) mono_hwcap_x86_has_avx = TRUE; } - if (cpuid (0x80000000, &eax, &ebx, &ecx, &edx)) { + if (mono_hwcap_x86_call_cpuidex (0x80000000, 0, &eax, &ebx, &ecx, &edx)) { if ((unsigned int) eax >= 0x80000001 && ebx == 0x68747541 && ecx == 0x444D4163 && edx == 0x69746E65) { - if (cpuid (0x80000001, &eax, &ebx, &ecx, &edx)) { + if (mono_hwcap_x86_call_cpuidex (0x80000001, 0, &eax, &ebx, &ecx, &edx)) { if (ecx & (1 << 6)) mono_hwcap_x86_has_sse4a = TRUE; } } } - if (cpuid (0x80000001, &eax, &ebx, &ecx, &edx)) { + if (mono_hwcap_x86_call_cpuidex (0x80000001, 0, &eax, &ebx, &ecx, &edx)) { if (ecx & (1 << 5)) mono_hwcap_x86_has_lzcnt = TRUE; } diff --git a/mono/utils/mono-mmap.c b/mono/utils/mono-mmap.c index 2dff889c4ca0..fa4686f0acac 100644 --- a/mono/utils/mono-mmap.c +++ b/mono/utils/mono-mmap.c @@ -305,6 +305,9 @@ mono_valloc (void *addr, size_t length, int flags, MonoMemAccountType type) } if ((flags & MONO_MMAP_JIT) && (use_mmap_jit || is_hardened_runtime == 1)) mflags |= MAP_JIT; + /* Patching code on apple silicon seems to cause random crashes without this flag */ + if (__builtin_available (macOS 11, *)) + mflags |= MAP_JIT; } #endif diff --git a/mono/utils/mono-threads-wasm.c b/mono/utils/mono-threads-wasm.c index e0b20fe1a8c9..33f4688fac64 100644 --- a/mono/utils/mono-threads-wasm.c +++ b/mono/utils/mono-threads-wasm.c @@ -2,7 +2,7 @@ #include #include - +#include #if defined (USE_WASM_BACKEND) @@ -319,4 +319,8 @@ mono_memory_barrier_process_wide (void) { } +#else + +MONO_EMPTY_SOURCE_FILE (mono_threads_wasm); + #endif diff --git a/mono/utils/mono-threads-windows.c b/mono/utils/mono-threads-windows.c index 8e94bf2db258..0ec80177326a 100644 --- a/mono/utils/mono-threads-windows.c +++ b/mono/utils/mono-threads-windows.c @@ -103,7 +103,7 @@ abort_apc (ULONG_PTR param) // thread is going away meaning that no more IO calls will be issued against the // same resource that was part of the cancelation. Current implementation of // .NET Framework and .NET Core currently don't support the ability to abort a thread -// blocked on sync IO calls, see https://github.com/dotnet/corefx/issues/5749. +// blocked on sync IO calls, see https://github.com/dotnet/runtime/issues/16236. // Since there is no solution covering all scenarios aborting blocking syscall this // will be on best effort and there might still be a slight risk that the blocking call // won't abort (depending on overlapped IO support for current file, socket). diff --git a/sdks/android/Makefile b/sdks/android/Makefile index 03bf58a1ca3e..ef9550e209ac 100644 --- a/sdks/android/Makefile +++ b/sdks/android/Makefile @@ -5,8 +5,8 @@ include $(TOP)/sdks/paths.mk UNAME=$(shell uname) -SDK_DIR = $(ANDROID_TOOLCHAIN_DIR)/sdk -NDK_DIR = $(ANDROID_TOOLCHAIN_DIR)/ndk +SDK_DIR = $(or $(ANDROID_SDK_ROOT),$(ANDROID_TOOLCHAIN_DIR)/sdk) +NDK_DIR = $(or $(ANDROID_NDK_ROOT),$(ANDROID_TOOLCHAIN_DIR)/ndk) ADB = $(SDK_DIR)/platform-tools/adb @@ -62,7 +62,7 @@ shell: .PHONY: accept-android-license accept-android-license: - yes | $(SDK_DIR)/tools/bin/sdkmanager --licenses + yes | $(SDK_DIR)/tools/bin/sdkmanager --sdk_root=$(SDK_DIR) --licenses ## Check targets diff --git a/sdks/builds/wasm.mk b/sdks/builds/wasm.mk index 5aaf58041890..077c6b6b0624 100644 --- a/sdks/builds/wasm.mk +++ b/sdks/builds/wasm.mk @@ -1,7 +1,7 @@ #emcc has lots of bash'isms SHELL:=/bin/bash -EMSCRIPTEN_VERSION=1.40.0 +EMSCRIPTEN_VERSION=2.0.1 EMSCRIPTEN_LOCAL_SDK_DIR=$(TOP)/sdks/builds/toolchains/emsdk EMSCRIPTEN_SDK_DIR ?= $(EMSCRIPTEN_LOCAL_SDK_DIR) diff --git a/sdks/wasm/ProxyDriver/ProxyDriver.csproj b/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj similarity index 50% rename from sdks/wasm/ProxyDriver/ProxyDriver.csproj rename to sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj index c0551057f5ad..aee852c016ed 100644 --- a/sdks/wasm/ProxyDriver/ProxyDriver.csproj +++ b/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj @@ -5,11 +5,9 @@ - + - - - + diff --git a/sdks/wasm/BrowserDebugHost/Program.cs b/sdks/wasm/BrowserDebugHost/Program.cs new file mode 100644 index 000000000000..442e5ac1fd57 --- /dev/null +++ b/sdks/wasm/BrowserDebugHost/Program.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.WebAssembly.Diagnostics +{ + public class ProxyOptions + { + public Uri DevToolsUrl { get; set; } = new Uri("http://localhost:9222"); + } + + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseSetting("UseIISIntegration", false.ToString()) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddCommandLine(args); + }) + .UseUrls("http://localhost:9300") + .Build(); + + host.Run(); + } + } +} diff --git a/sdks/wasm/ProxyDriver/Properties/launchSettings.json b/sdks/wasm/BrowserDebugHost/Properties/launchSettings.json similarity index 95% rename from sdks/wasm/ProxyDriver/Properties/launchSettings.json rename to sdks/wasm/BrowserDebugHost/Properties/launchSettings.json index 22258c4cb66d..0acb48fc9728 100644 --- a/sdks/wasm/ProxyDriver/Properties/launchSettings.json +++ b/sdks/wasm/BrowserDebugHost/Properties/launchSettings.json @@ -15,7 +15,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "ProxyDriver": { + "BrowserDebugHost": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { diff --git a/sdks/wasm/BrowserDebugHost/Startup.cs b/sdks/wasm/BrowserDebugHost/Startup.cs new file mode 100644 index 000000000000..81c70187ed07 --- /dev/null +++ b/sdks/wasm/BrowserDebugHost/Startup.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) => + services.AddRouting() + .Configure(Configuration); + + public Startup(IConfiguration configuration) => + Configuration = configuration; + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env) + { + var options = optionsAccessor.CurrentValue; + app.UseDeveloperExceptionPage() + .UseWebSockets() + .UseDebugProxy(options); + } + } + + static class DebugExtensions + { + public static Dictionary MapValues(Dictionary response, HttpContext context, Uri debuggerHost) + { + var filtered = new Dictionary(); + var request = context.Request; + + foreach (var key in response.Keys) + { + switch (key) + { + case "devtoolsFrontendUrl": + var front = response[key]; + filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; + break; + case "webSocketDebuggerUrl": + var page = new Uri(response[key]); + filtered[key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}"; + break; + default: + filtered[key] = response[key]; + break; + } + } + return filtered; + } + + public static IApplicationBuilder UseDebugProxy(this IApplicationBuilder app, ProxyOptions options) => + UseDebugProxy(app, options, MapValues); + + public static IApplicationBuilder UseDebugProxy( + this IApplicationBuilder app, + ProxyOptions options, + Func, HttpContext, Uri, Dictionary> mapFunc) + { + var devToolsHost = options.DevToolsUrl; + app.UseRouter(router => + { + router.MapGet("/", Copy); + router.MapGet("/favicon.ico", Copy); + router.MapGet("json", RewriteArray); + router.MapGet("json/list", RewriteArray); + router.MapGet("json/version", RewriteSingle); + router.MapGet("json/new", RewriteSingle); + router.MapGet("devtools/page/{pageId}", ConnectProxy); + router.MapGet("devtools/browser/{pageId}", ConnectProxy); + + string GetEndpoint(HttpContext context) + { + var request = context.Request; + var requestPath = request.Path; + return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}"; + } + + async Task Copy(HttpContext context) + { + using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }) + { + var response = await httpClient.GetAsync(GetEndpoint(context)); + context.Response.ContentType = response.Content.Headers.ContentType.ToString(); + if ((response.Content.Headers.ContentLength ?? 0) > 0) + context.Response.ContentLength = response.Content.Headers.ContentLength; + var bytes = await response.Content.ReadAsByteArrayAsync(); + await context.Response.Body.WriteAsync(bytes); + + } + } + + async Task RewriteSingle(HttpContext context) + { + var version = await ProxyGetJsonAsync>(GetEndpoint(context)); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync( + JsonSerializer.Serialize(mapFunc(version, context, devToolsHost))); + } + + async Task RewriteArray(HttpContext context) + { + var tabs = await ProxyGetJsonAsync[]>(GetEndpoint(context)); + var alteredTabs = tabs.Select(t => mapFunc(t, context, devToolsHost)).ToArray(); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(alteredTabs)); + } + + async Task ConnectProxy(HttpContext context) + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + var endpoint = new Uri($"ws://{devToolsHost.Authority}{context.Request.Path.ToString()}"); + try + { + using var loggerFactory = LoggerFactory.Create( + builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); + var proxy = new DebuggerProxy(loggerFactory); + var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); + + await proxy.Run(endpoint, ideSocket); + } + catch (Exception e) + { + Console.WriteLine("got exception {0}", e); + } + } + }); + return app; + } + + static async Task ProxyGetJsonAsync(string url) + { + using (var httpClient = new HttpClient()) + { + var response = await httpClient.GetAsync(url); + return await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); + } + } + } +} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.csproj b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj similarity index 89% rename from sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.csproj rename to sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj index 4063416ceb07..4ac612f127a8 100644 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.csproj +++ b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + netcoreapp3.0 true diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.sln b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.sln similarity index 80% rename from sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.sln rename to sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.sln index c99f5389610b..cfb208d1562f 100644 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/Mono.WebAssembly.DebuggerProxy.sln +++ b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28407.52 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxyDriver", "..\ProxyDriver\ProxyDriver.csproj", "{954F768A-23E6-4B14-90E0-27EA6B41FBCC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugHost", "..\BrowserDebugHost\BrowserDebugHost.csproj", "{954F768A-23E6-4B14-90E0-27EA6B41FBCC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.DebuggerProxy", "Mono.WebAssembly.DebuggerProxy.csproj", "{490128B6-9F21-46CA-878A-F22BCF51EF3C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugProxy", "BrowserDebugProxy.csproj", "{490128B6-9F21-46CA-878A-F22BCF51EF3C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/sdks/wasm/BrowserDebugProxy/DebugStore.cs b/sdks/wasm/BrowserDebugProxy/DebugStore.cs new file mode 100644 index 000000000000..9f9c7a6bd080 --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/DebugStore.cs @@ -0,0 +1,883 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Pdb; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BreakpointRequest + { + public string Id { get; private set; } + public string Assembly { get; private set; } + public string File { get; private set; } + public int Line { get; private set; } + public int Column { get; private set; } + public MethodInfo Method { get; private set; } + + JObject request; + + public bool IsResolved => Assembly != null; + public List Locations { get; } = new List(); + + public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; + + public object AsSetBreakpointByUrlResponse(IEnumerable jsloc) => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation()).Concat(jsloc) }; + + public BreakpointRequest() + { } + + public BreakpointRequest(string id, MethodInfo method) + { + Id = id; + Method = method; + } + + public BreakpointRequest(string id, JObject request) + { + Id = id; + this.request = request; + } + + public static BreakpointRequest Parse(string id, JObject args) + { + return new BreakpointRequest(id, args); + } + + public BreakpointRequest Clone() => new BreakpointRequest { Id = Id, request = request }; + + public bool IsMatch(SourceFile sourceFile) + { + var url = request?["url"]?.Value(); + if (url == null) + { + var urlRegex = request?["urlRegex"].Value(); + var regex = new Regex(urlRegex); + return regex.IsMatch(sourceFile.Url.ToString()) || regex.IsMatch(sourceFile.DocUrl); + } + + return sourceFile.Url.ToString() == url || sourceFile.DotNetUrl == url; + } + + public bool TryResolve(SourceFile sourceFile) + { + if (!IsMatch(sourceFile)) + return false; + + var line = request?["lineNumber"]?.Value(); + var column = request?["columnNumber"]?.Value(); + + if (line == null || column == null) + return false; + + Assembly = sourceFile.AssemblyName; + File = sourceFile.DebuggerFileName; + Line = line.Value; + Column = column.Value; + return true; + } + + public bool TryResolve(DebugStore store) + { + if (request == null || store == null) + return false; + + return store.AllSources().FirstOrDefault(source => TryResolve(source)) != null; + } + } + + internal class VarInfo + { + public VarInfo(VariableDebugInformation v) + { + this.Name = v.Name; + this.Index = v.Index; + } + + public VarInfo(ParameterDefinition p) + { + this.Name = p.Name; + this.Index = (p.Index + 1) * -1; + } + + public string Name { get; } + public int Index { get; } + + public override string ToString() => $"(var-info [{Index}] '{Name}')"; + } + + internal class CliLocation + { + public CliLocation(MethodInfo method, int offset) + { + Method = method; + Offset = offset; + } + + public MethodInfo Method { get; } + public int Offset { get; } + } + + internal class SourceLocation + { + SourceId id; + int line; + int column; + CliLocation cliLoc; + + public SourceLocation(SourceId id, int line, int column) + { + this.id = id; + this.line = line; + this.column = column; + } + + public SourceLocation(MethodInfo mi, SequencePoint sp) + { + this.id = mi.SourceId; + this.line = sp.StartLine - 1; + this.column = sp.StartColumn - 1; + this.cliLoc = new CliLocation(mi, sp.Offset); + } + + public SourceId Id { get => id; } + public int Line { get => line; } + public int Column { get => column; } + public CliLocation CliLocation => this.cliLoc; + + public override string ToString() => $"{id}:{Line}:{Column}"; + + public static SourceLocation Parse(JObject obj) + { + if (obj == null) + return null; + + if (!SourceId.TryParse(obj["scriptId"]?.Value(), out var id)) + return null; + + var line = obj["lineNumber"]?.Value(); + var column = obj["columnNumber"]?.Value(); + if (id == null || line == null || column == null) + return null; + + return new SourceLocation(id, line.Value, column.Value); + } + + internal class LocationComparer : EqualityComparer + { + public override bool Equals(SourceLocation l1, SourceLocation l2) + { + if (l1 == null && l2 == null) + return true; + else if (l1 == null || l2 == null) + return false; + + return (l1.Line == l2.Line && + l1.Column == l2.Column && + l1.Id == l2.Id); + } + + public override int GetHashCode(SourceLocation loc) + { + int hCode = loc.Line ^ loc.Column; + return loc.Id.GetHashCode() ^ hCode.GetHashCode(); + } + } + + internal object AsLocation() => new + { + scriptId = id.ToString(), + lineNumber = line, + columnNumber = column + }; + } + + internal class SourceId + { + const string Scheme = "dotnet://"; + + readonly int assembly, document; + + public int Assembly => assembly; + public int Document => document; + + internal SourceId(int assembly, int document) + { + this.assembly = assembly; + this.document = document; + } + + public SourceId(string id) + { + if (!TryParse(id, out assembly, out document)) + throw new ArgumentException("invalid source identifier", nameof(id)); + } + + public static bool TryParse(string id, out SourceId source) + { + source = null; + if (!TryParse(id, out var assembly, out var document)) + return false; + + source = new SourceId(assembly, document); + return true; + } + + static bool TryParse(string id, out int assembly, out int document) + { + assembly = document = 0; + if (id == null || !id.StartsWith(Scheme, StringComparison.Ordinal)) + return false; + + var sp = id.Substring(Scheme.Length).Split('_'); + if (sp.Length != 2) + return false; + + if (!int.TryParse(sp[0], out assembly)) + return false; + + if (!int.TryParse(sp[1], out document)) + return false; + + return true; + } + + public override string ToString() => $"{Scheme}{assembly}_{document}"; + + public override bool Equals(object obj) + { + if (obj == null) + return false; + SourceId that = obj as SourceId; + return that.assembly == this.assembly && that.document == this.document; + } + + public override int GetHashCode() => assembly.GetHashCode() ^ document.GetHashCode(); + + public static bool operator ==(SourceId a, SourceId b) => ((object)a == null) ? (object)b == null : a.Equals(b); + + public static bool operator !=(SourceId a, SourceId b) => !a.Equals(b); + } + + internal class MethodInfo + { + MethodDefinition methodDef; + SourceFile source; + + public SourceId SourceId => source.SourceId; + + public string Name => methodDef.Name; + public MethodDebugInformation DebugInformation => methodDef.DebugInformation; + + public SourceLocation StartLocation { get; } + public SourceLocation EndLocation { get; } + public AssemblyInfo Assembly { get; } + public uint Token => methodDef.MetadataToken.RID; + + public MethodInfo(AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) + { + this.Assembly = assembly; + this.methodDef = methodDef; + this.source = source; + + var sps = DebugInformation.SequencePoints; + if (sps == null || sps.Count() < 1) + return; + + SequencePoint start = sps[0]; + SequencePoint end = sps[0]; + + foreach (var sp in sps) + { + if (sp.StartLine < start.StartLine) + start = sp; + else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn) + start = sp; + + if (sp.EndLine > end.EndLine) + end = sp; + else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn) + end = sp; + } + + StartLocation = new SourceLocation(this, start); + EndLocation = new SourceLocation(this, end); + } + + public SourceLocation GetLocationByIl(int pos) + { + SequencePoint prev = null; + foreach (var sp in DebugInformation.SequencePoints) + { + if (sp.Offset > pos) + break; + prev = sp; + } + + if (prev != null) + return new SourceLocation(this, prev); + + return null; + } + + public VarInfo[] GetLiveVarsAt(int offset) + { + var res = new List(); + + res.AddRange(methodDef.Parameters.Select(p => new VarInfo(p))); + res.AddRange(methodDef.DebugInformation.GetScopes() + .Where(s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) + .SelectMany(s => s.Variables) + .Where(v => !v.IsDebuggerHidden) + .Select(v => new VarInfo(v))); + + return res.ToArray(); + } + + public override string ToString() => "MethodInfo(" + methodDef.FullName + ")"; + } + + internal class TypeInfo + { + AssemblyInfo assembly; + TypeDefinition type; + List methods; + + public TypeInfo(AssemblyInfo assembly, TypeDefinition type) + { + this.assembly = assembly; + this.type = type; + methods = new List(); + } + + public string Name => type.Name; + public string FullName => type.FullName; + public List Methods => methods; + + public override string ToString() => "TypeInfo('" + FullName + "')"; + } + + class AssemblyInfo + { + static int next_id; + ModuleDefinition image; + readonly int id; + readonly ILogger logger; + Dictionary methods = new Dictionary(); + Dictionary sourceLinkMappings = new Dictionary(); + Dictionary typesByName = new Dictionary(); + readonly List sources = new List(); + internal string Url { get; } + + public AssemblyInfo(IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb) + { + this.id = Interlocked.Increment(ref next_id); + + try + { + Url = url; + ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ ); + rp.AssemblyResolver = resolver; + // set ReadSymbols = true unconditionally in case there + // is an embedded pdb then handle ArgumentException + // and assume that if pdb == null that is the cause + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new PdbReaderProvider(); + if (pdb != null) + rp.SymbolStream = new MemoryStream(pdb); + rp.ReadingMode = ReadingMode.Immediate; + + this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp); + } + catch (BadImageFormatException ex) + { + logger.LogWarning($"Failed to read assembly as portable PDB: {ex.Message}"); + } + catch (ArgumentException) + { + // if pdb == null this is expected and we + // read the assembly without symbols below + if (pdb != null) + throw; + } + + if (this.image == null) + { + ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ ); + rp.AssemblyResolver = resolver; + if (pdb != null) + { + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new PdbReaderProvider(); + rp.SymbolStream = new MemoryStream(pdb); + } + + rp.ReadingMode = ReadingMode.Immediate; + + this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp); + } + + Populate(); + } + + public AssemblyInfo(ILogger logger) + { + this.logger = logger; + } + + void Populate() + { + ProcessSourceLink(); + + var d2s = new Dictionary(); + + SourceFile FindSource(Document doc) + { + if (doc == null) + return null; + + if (d2s.TryGetValue(doc, out var source)) + return source; + + var src = new SourceFile(this, sources.Count, doc, GetSourceLinkUrl(doc.Url)); + sources.Add(src); + d2s[doc] = src; + return src; + }; + + foreach (var type in image.GetTypes()) + { + var typeInfo = new TypeInfo(this, type); + typesByName[type.FullName] = typeInfo; + + foreach (var method in type.Methods) + { + foreach (var sp in method.DebugInformation.SequencePoints) + { + var source = FindSource(sp.Document); + var methodInfo = new MethodInfo(this, method, source); + methods[method.MetadataToken.RID] = methodInfo; + if (source != null) + source.AddMethod(methodInfo); + + typeInfo.Methods.Add(methodInfo); + } + } + } + } + + private void ProcessSourceLink() + { + var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault(i => i.Kind == CustomDebugInformationKind.SourceLink); + + if (sourceLinkDebugInfo != null) + { + var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + + if (sourceLinkContent != null) + { + var jObject = JObject.Parse(sourceLinkContent)["documents"]; + sourceLinkMappings = JsonConvert.DeserializeObject>(jObject.ToString()); + } + } + } + + private Uri GetSourceLinkUrl(string document) + { + if (sourceLinkMappings.TryGetValue(document, out string url)) + return new Uri(url); + + foreach (var sourceLinkDocument in sourceLinkMappings) + { + string key = sourceLinkDocument.Key; + + if (Path.GetFileName(key) != "*") + { + continue; + } + + var keyTrim = key.TrimEnd('*'); + + if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) + { + var docUrlPart = document.Replace(keyTrim, ""); + return new Uri(sourceLinkDocument.Value.TrimEnd('*') + docUrlPart); + } + } + + return null; + } + + public IEnumerable Sources => this.sources; + + public Dictionary TypesByName => this.typesByName; + public int Id => id; + public string Name => image.Name; + + public SourceFile GetDocById(int document) + { + return sources.FirstOrDefault(s => s.SourceId.Document == document); + } + + public MethodInfo GetMethodByToken(uint token) + { + methods.TryGetValue(token, out var value); + return value; + } + + public TypeInfo GetTypeByName(string name) + { + typesByName.TryGetValue(name, out var res); + return res; + } + } + + internal class SourceFile + { + Dictionary methods; + AssemblyInfo assembly; + int id; + Document doc; + + internal SourceFile(AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) + { + this.methods = new Dictionary(); + this.SourceLinkUri = sourceLinkUri; + this.assembly = assembly; + this.id = id; + this.doc = doc; + this.DebuggerFileName = doc.Url.Replace("\\", "/").Replace(":", ""); + + this.SourceUri = new Uri((Path.IsPathRooted(doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute); + if (SourceUri.IsFile && File.Exists(SourceUri.LocalPath)) + { + this.Url = this.SourceUri.ToString(); + } + else + { + this.Url = DotNetUrl; + } + } + + internal void AddMethod(MethodInfo mi) + { + if (!this.methods.ContainsKey(mi.Token)) + this.methods[mi.Token] = mi; + } + + public string DebuggerFileName { get; } + public string Url { get; } + public string AssemblyName => assembly.Name; + public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}"; + + public SourceId SourceId => new SourceId(assembly.Id, this.id); + public Uri SourceLinkUri { get; } + public Uri SourceUri { get; } + + public IEnumerable Methods => this.methods.Values; + + public string DocUrl => doc.Url; + + public (int startLine, int startColumn, int endLine, int endColumn) GetExtents() + { + var start = Methods.OrderBy(m => m.StartLocation.Line).ThenBy(m => m.StartLocation.Column).First(); + var end = Methods.OrderByDescending(m => m.EndLocation.Line).ThenByDescending(m => m.EndLocation.Column).First(); + return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column); + } + + async Task GetDataAsync(Uri uri, CancellationToken token) + { + var mem = new MemoryStream(); + try + { + if (uri.IsFile && File.Exists(uri.LocalPath)) + { + using (var file = File.Open(SourceUri.LocalPath, FileMode.Open)) + { + await file.CopyToAsync(mem, token).ConfigureAwait(false); + mem.Position = 0; + } + } + else if (uri.Scheme == "http" || uri.Scheme == "https") + { + var client = new HttpClient(); + using (var stream = await client.GetStreamAsync(uri)) + { + await stream.CopyToAsync(mem, token).ConfigureAwait(false); + mem.Position = 0; + } + } + } + catch (Exception) + { + return null; + } + return mem; + } + + static HashAlgorithm GetHashAlgorithm(DocumentHashAlgorithm algorithm) + { + switch (algorithm) + { + case DocumentHashAlgorithm.SHA1: + return SHA1.Create(); + case DocumentHashAlgorithm.SHA256: + return SHA256.Create(); + case DocumentHashAlgorithm.MD5: + return MD5.Create(); + } + return null; + } + + bool CheckPdbHash(byte[] computedHash) + { + if (computedHash.Length != doc.Hash.Length) + return false; + + for (var i = 0; i < computedHash.Length; i++) + if (computedHash[i] != doc.Hash[i]) + return false; + + return true; + } + + byte[] ComputePdbHash(Stream sourceStream) + { + var algorithm = GetHashAlgorithm(doc.HashAlgorithm); + if (algorithm != null) + using (algorithm) + return algorithm.ComputeHash(sourceStream); + + return Array.Empty(); + } + + public async Task GetSourceAsync(bool checkHash, CancellationToken token = default(CancellationToken)) + { + if (doc.EmbeddedSource.Length > 0) + return new MemoryStream(doc.EmbeddedSource, false); + + foreach (var url in new[] { SourceUri, SourceLinkUri }) + { + var mem = await GetDataAsync(url, token).ConfigureAwait(false); + if (mem != null && (!checkHash || CheckPdbHash(ComputePdbHash(mem)))) + { + mem.Position = 0; + return mem; + } + } + + return MemoryStream.Null; + } + + public object ToScriptSource(int executionContextId, object executionContextAuxData) + { + return new + { + scriptId = SourceId.ToString(), + url = Url, + executionContextId, + executionContextAuxData, + //hash: should be the v8 hash algo, managed implementation is pending + dotNetUrl = DotNetUrl, + }; + } + } + + internal class DebugStore + { + List assemblies = new List(); + readonly HttpClient client; + readonly ILogger logger; + + public DebugStore(ILogger logger, HttpClient client) + { + this.client = client; + this.logger = logger; + } + + public DebugStore(ILogger logger) : this(logger, new HttpClient()) + { } + + class DebugItem + { + public string Url { get; set; } + public Task Data { get; set; } + } + + public async IAsyncEnumerable Load(SessionId sessionId, string[] loaded_files, [EnumeratorCancellation] CancellationToken token) + { + static bool MatchPdb(string asm, string pdb) => Path.ChangeExtension(asm, "pdb") == pdb; + + var asm_files = new List(); + var pdb_files = new List(); + foreach (var file_name in loaded_files) + { + if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) + pdb_files.Add(file_name); + else + asm_files.Add(file_name); + } + + List steps = new List(); + foreach (var url in asm_files) + { + try + { + var pdb = pdb_files.FirstOrDefault(n => MatchPdb(url, n)); + steps.Add( + new DebugItem + { + Url = url, + Data = Task.WhenAll(client.GetByteArrayAsync(url), pdb != null ? client.GetByteArrayAsync(pdb) : Task.FromResult(null)) + }); + } + catch (Exception e) + { + logger.LogDebug($"Failed to read {url} ({e.Message})"); + } + } + + var resolver = new DefaultAssemblyResolver(); + foreach (var step in steps) + { + AssemblyInfo assembly = null; + try + { + var bytes = await step.Data.ConfigureAwait(false); + assembly = new AssemblyInfo(resolver, step.Url, bytes[0], bytes[1]); + } + catch (Exception e) + { + logger.LogDebug($"Failed to load {step.Url} ({e.Message})"); + } + if (assembly == null) + continue; + + assemblies.Add(assembly); + foreach (var source in assembly.Sources) + yield return source; + } + } + + public IEnumerable AllSources() => assemblies.SelectMany(a => a.Sources); + + public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id)); + + public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + + /* + V8 uses zero based indexing for both line and column. + PPDBs uses one based indexing for both line and column. + */ + static bool Match(SequencePoint sp, SourceLocation start, SourceLocation end) + { + var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); + var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); + + if (start.Line > spEnd.Line) + return false; + + if (start.Column > spEnd.Column && start.Line == spEnd.Line) + return false; + + if (end.Line < spStart.Line) + return false; + + if (end.Column < spStart.Column && end.Line == spStart.Line) + return false; + + return true; + } + + public List FindPossibleBreakpoints(SourceLocation start, SourceLocation end) + { + //XXX FIXME no idea what todo with locations on different files + if (start.Id != end.Id) + { + logger.LogDebug($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}"); + return null; + } + + var sourceId = start.Id; + + var doc = GetFileById(sourceId); + + var res = new List(); + if (doc == null) + { + logger.LogDebug($"Could not find document {sourceId}"); + return res; + } + + foreach (var method in doc.Methods) + { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) + { + if (!sequencePoint.IsHidden && Match(sequencePoint, start, end)) + res.Add(new SourceLocation(method, sequencePoint)); + } + } + return res; + } + + /* + V8 uses zero based indexing for both line and column. + PPDBs uses one based indexing for both line and column. + */ + static bool Match(SequencePoint sp, int line, int column) + { + var bp = (line: line + 1, column: column + 1); + + if (sp.StartLine > bp.line || sp.EndLine < bp.line) + return false; + + //Chrome sends a zero column even if getPossibleBreakpoints say something else + if (column == 0) + return true; + + if (sp.StartColumn > bp.column && sp.StartLine == bp.line) + return false; + + if (sp.EndColumn < bp.column && sp.EndLine == bp.line) + return false; + + return true; + } + + public IEnumerable FindBreakpointLocations(BreakpointRequest request) + { + request.TryResolve(this); + + var asm = assemblies.FirstOrDefault(a => a.Name.Equals(request.Assembly, StringComparison.OrdinalIgnoreCase)); + var sourceFile = asm?.Sources?.SingleOrDefault(s => s.DebuggerFileName.Equals(request.File, StringComparison.OrdinalIgnoreCase)); + + if (sourceFile == null) + yield break; + + foreach (var method in sourceFile.Methods) + { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) + { + if (!sequencePoint.IsHidden && Match(sequencePoint, request.Line, request.Column)) + yield return new SourceLocation(method, sequencePoint); + } + } + } + + public string ToUrl(SourceLocation location) => location != null ? GetFileById(location.Id).Url : ""; + } +} diff --git a/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs b/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs new file mode 100644 index 000000000000..08fe836200dc --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.WebAssembly.Diagnostics +{ + + // This type is the public entrypoint that allows external code to attach the debugger proxy + // to a given websocket listener. Everything else in this package can be internal. + + public class DebuggerProxy + { + private readonly MonoProxy proxy; + + public DebuggerProxy(ILoggerFactory loggerFactory) + { + proxy = new MonoProxy(loggerFactory); + } + + public Task Run(Uri browserUri, WebSocket ideSocket) + { + return proxy.Run(browserUri, ideSocket); + } + } +} diff --git a/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs b/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs new file mode 100644 index 000000000000..9a5761f60c32 --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + + public struct SessionId + { + public readonly string sessionId; + + public SessionId(string sessionId) + { + this.sessionId = sessionId; + } + + // hashset treats 0 as unset + public override int GetHashCode() => sessionId?.GetHashCode() ?? -1; + + public override bool Equals(object obj) => (obj is SessionId) ? ((SessionId)obj).sessionId == sessionId : false; + + public static bool operator ==(SessionId a, SessionId b) => a.sessionId == b.sessionId; + + public static bool operator !=(SessionId a, SessionId b) => a.sessionId != b.sessionId; + + public static SessionId Null { get; } = new SessionId(); + + public override string ToString() => $"session-{sessionId}"; + } + + public struct MessageId + { + public readonly string sessionId; + public readonly int id; + + public MessageId(string sessionId, int id) + { + this.sessionId = sessionId; + this.id = id; + } + + public static implicit operator SessionId(MessageId id) => new SessionId(id.sessionId); + + public override string ToString() => $"msg-{sessionId}:::{id}"; + + public override int GetHashCode() => (sessionId?.GetHashCode() ?? 0) ^ id.GetHashCode(); + + public override bool Equals(object obj) => (obj is MessageId) ? ((MessageId)obj).sessionId == sessionId && ((MessageId)obj).id == id : false; + } + + internal class DotnetObjectId + { + public string Scheme { get; } + public string Value { get; } + + public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); + + public static bool TryParse(string id, out DotnetObjectId objectId) + { + objectId = null; + if (id == null) + return false; + + if (!id.StartsWith("dotnet:")) + return false; + + var parts = id.Split(":", 3); + + if (parts.Length < 3) + return false; + + objectId = new DotnetObjectId(parts[1], parts[2]); + + return true; + } + + public DotnetObjectId(string scheme, string value) + { + Scheme = scheme; + Value = value; + } + + public override string ToString() => $"dotnet:{Scheme}:{Value}"; + } + + public struct Result + { + public JObject Value { get; private set; } + public JObject Error { get; private set; } + + public bool IsOk => Value != null; + public bool IsErr => Error != null; + + Result(JObject result, JObject error) + { + if (result != null && error != null) + throw new ArgumentException($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null."); + + bool resultHasError = String.Compare((result?["result"] as JObject)?["subtype"]?.Value(), "error") == 0; + if (result != null && resultHasError) + { + this.Value = null; + this.Error = result; + } + else + { + this.Value = result; + this.Error = error; + } + } + + public static Result FromJson(JObject obj) + { + //Log ("protocol", $"from result: {obj}"); + return new Result(obj["result"] as JObject, obj["error"] as JObject); + } + + public static Result Ok(JObject ok) => new Result(ok, null); + + public static Result OkFromObject(object ok) => Ok(JObject.FromObject(ok)); + + public static Result Err(JObject err) => new Result(null, err); + + public static Result Err(string msg) => new Result(null, JObject.FromObject(new { message = msg })); + + public static Result Exception(Exception e) => new Result(null, JObject.FromObject(new { message = e.Message })); + + public JObject ToJObject(MessageId target) + { + if (IsOk) + { + return JObject.FromObject(new + { + target.id, + target.sessionId, + result = Value + }); + } + else + { + return JObject.FromObject(new + { + target.id, + target.sessionId, + error = Error + }); + } + } + + public override string ToString() + { + return $"[Result: IsOk: {IsOk}, IsErr: {IsErr}, Value: {Value?.ToString()}, Error: {Error?.ToString()} ]"; + } + } + + internal class MonoCommands + { + public string expression { get; set; } + public string objectGroup { get; set; } = "mono-debugger"; + public bool includeCommandLineAPI { get; set; } = false; + public bool silent { get; set; } = false; + public bool returnByValue { get; set; } = true; + + public MonoCommands(string expression) => this.expression = expression; + + public static MonoCommands GetCallStack() => new MonoCommands("MONO.mono_wasm_get_call_stack()"); + + public static MonoCommands GetExceptionObject() => new MonoCommands("MONO.mono_wasm_get_exception_object()"); + + public static MonoCommands IsRuntimeReady() => new MonoCommands("MONO.mono_wasm_runtime_is_ready"); + + public static MonoCommands StartSingleStepping(StepKind kind) => new MonoCommands($"MONO.mono_wasm_start_single_stepping ({(int)kind})"); + + public static MonoCommands GetLoadedFiles() => new MonoCommands("MONO.mono_wasm_get_loaded_files()"); + + public static MonoCommands ClearAllBreakpoints() => new MonoCommands("MONO.mono_wasm_clear_all_breakpoints()"); + + public static MonoCommands GetDetails(DotnetObjectId objectId, JToken args = null) => new MonoCommands($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{ }")})"); + + public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars) + { + var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray(); + return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})"); + } + + public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars) + { + var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray(); + return new MonoCommands($"MONO.mono_wasm_eval_member_access({scopeId}, {JsonConvert.SerializeObject(var_ids)}, '', '{expr}')"); + } + + public static MonoCommands SetBreakpoint(string assemblyName, uint methodToken, int ilOffset) => new MonoCommands($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})"); + + public static MonoCommands RemoveBreakpoint(int breakpointId) => new MonoCommands($"MONO.mono_wasm_remove_breakpoint({breakpointId})"); + + public static MonoCommands ReleaseObject(DotnetObjectId objectId) => new MonoCommands($"MONO.mono_wasm_release_object('{objectId}')"); + + public static MonoCommands CallFunctionOn(JToken args) => new MonoCommands($"MONO.mono_wasm_call_function_on ({args.ToString()})"); + + public static MonoCommands Resume() => new MonoCommands($"MONO.mono_wasm_debugger_resume ()"); + + public static MonoCommands SetPauseOnExceptions(string state) => new MonoCommands($"MONO.mono_wasm_set_pause_on_exceptions(\"{state}\")"); + } + + internal enum MonoErrorCodes + { + BpNotFound = 100000, + } + + internal class MonoConstants + { + public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready"; + } + + class Frame + { + public Frame(MethodInfo method, SourceLocation location, int id) + { + this.Method = method; + this.Location = location; + this.Id = id; + } + + public MethodInfo Method { get; private set; } + public SourceLocation Location { get; private set; } + public int Id { get; private set; } + } + + class Breakpoint + { + public SourceLocation Location { get; private set; } + public int RemoteId { get; set; } + public BreakpointState State { get; set; } + public string StackId { get; private set; } + + public static bool TryParseId(string stackId, out int id) + { + id = -1; + if (stackId?.StartsWith("dotnet:", StringComparison.Ordinal) != true) + return false; + + return int.TryParse(stackId.Substring("dotnet:".Length), out id); + } + + public Breakpoint(string stackId, SourceLocation loc, BreakpointState state) + { + this.StackId = stackId; + this.Location = loc; + this.State = state; + } + } + + enum BreakpointState + { + Active, + Disabled, + Pending + } + + enum StepKind + { + Into, + Out, + Over + } + + internal class ExecutionContext + { + public string DebuggerId { get; set; } + public Dictionary BreakpointRequests { get; } = new Dictionary(); + + public TaskCompletionSource ready = null; + public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; + + public int Id { get; set; } + public object AuxData { get; set; } + + public List CallStack { get; set; } + + public string[] LoadedFiles { get; set; } + internal DebugStore store; + public TaskCompletionSource Source { get; } = new TaskCompletionSource(); + + Dictionary perScopeCaches { get; } = new Dictionary(); + + public DebugStore Store + { + get + { + if (store == null || !Source.Task.IsCompleted) + return null; + + return store; + } + } + + public PerScopeCache GetCacheForScope(int scope_id) + { + if (perScopeCaches.TryGetValue(scope_id, out var cache)) + return cache; + + cache = new PerScopeCache(); + perScopeCaches[scope_id] = cache; + return cache; + } + + public void ClearState() + { + CallStack = null; + perScopeCaches.Clear(); + } + } + + internal class PerScopeCache + { + public Dictionary Locals { get; } = new Dictionary(); + public Dictionary MemberReferences { get; } = new Dictionary(); + } +} diff --git a/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs new file mode 100644 index 000000000000..dc399dfef38c --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs @@ -0,0 +1,381 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + + class DevToolsQueue + { + Task current_send; + List pending; + + public WebSocket Ws { get; private set; } + public Task CurrentSend { get { return current_send; } } + public DevToolsQueue(WebSocket sock) + { + this.Ws = sock; + pending = new List(); + } + + public Task Send(byte[] bytes, CancellationToken token) + { + pending.Add(bytes); + if (pending.Count == 1) + { + if (current_send != null) + throw new Exception("current_send MUST BE NULL IF THERE'S no pending send"); + //logger.LogTrace ("sending {0} bytes", bytes.Length); + current_send = Ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); + return current_send; + } + return null; + } + + public Task Pump(CancellationToken token) + { + current_send = null; + pending.RemoveAt(0); + + if (pending.Count > 0) + { + if (current_send != null) + throw new Exception("current_send MUST BE NULL IF THERE'S no pending send"); + + current_send = Ws.SendAsync(new ArraySegment(pending[0]), WebSocketMessageType.Text, true, token); + return current_send; + } + return null; + } + } + + internal class DevToolsProxy + { + TaskCompletionSource side_exception = new TaskCompletionSource(); + TaskCompletionSource client_initiated_close = new TaskCompletionSource(); + Dictionary> pending_cmds = new Dictionary>(); + ClientWebSocket browser; + WebSocket ide; + int next_cmd_id; + List pending_ops = new List(); + List queues = new List(); + + protected readonly ILogger logger; + + public DevToolsProxy(ILoggerFactory loggerFactory) + { + logger = loggerFactory.CreateLogger(); + } + + protected virtual Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + { + return Task.FromResult(false); + } + + protected virtual Task AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) + { + return Task.FromResult(false); + } + + async Task ReadOne(WebSocket socket, CancellationToken token) + { + byte[] buff = new byte[4000]; + var mem = new MemoryStream(); + while (true) + { + + if (socket.State != WebSocketState.Open) + { + Log("error", $"DevToolsProxy: Socket is no longer open."); + client_initiated_close.TrySetResult(true); + return null; + } + + var result = await socket.ReceiveAsync(new ArraySegment(buff), token); + if (result.MessageType == WebSocketMessageType.Close) + { + client_initiated_close.TrySetResult(true); + return null; + } + + mem.Write(buff, 0, result.Count); + + if (result.EndOfMessage) + return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); + } + } + + DevToolsQueue GetQueueForSocket(WebSocket ws) + { + return queues.FirstOrDefault(q => q.Ws == ws); + } + + DevToolsQueue GetQueueForTask(Task task) + { + return queues.FirstOrDefault(q => q.CurrentSend == task); + } + + void Send(WebSocket to, JObject o, CancellationToken token) + { + var sender = browser == to ? "Send-browser" : "Send-ide"; + + var method = o["method"]?.ToString(); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log("protocol", $"{sender}: " + JsonConvert.SerializeObject(o)); + var bytes = Encoding.UTF8.GetBytes(o.ToString()); + + var queue = GetQueueForSocket(to); + + var task = queue.Send(bytes, token); + if (task != null) + pending_ops.Add(task); + } + + async Task OnEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + { + try + { + if (!await AcceptEvent(sessionId, method, args, token)) + { + //logger.LogDebug ("proxy browser: {0}::{1}",method, args); + SendEventInternal(sessionId, method, args, token); + } + } + catch (Exception e) + { + side_exception.TrySetException(e); + } + } + + async Task OnCommand(MessageId id, string method, JObject args, CancellationToken token) + { + try + { + if (!await AcceptCommand(id, method, args, token)) + { + var res = await SendCommandInternal(id, method, args, token); + SendResponseInternal(id, res, token); + } + } + catch (Exception e) + { + side_exception.TrySetException(e); + } + } + + void OnResponse(MessageId id, Result result) + { + //logger.LogTrace ("got id {0} res {1}", id, result); + // Fixme + if (pending_cmds.Remove(id, out var task)) + { + task.SetResult(result); + return; + } + logger.LogError("Cannot respond to command: {id} with result: {result} - command is not pending", id, result); + } + + void ProcessBrowserMessage(string msg, CancellationToken token) + { + var res = JObject.Parse(msg); + + var method = res["method"]?.ToString(); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log("protocol", $"browser: {msg}"); + + if (res["id"] == null) + pending_ops.Add(OnEvent(new SessionId(res["sessionId"]?.Value()), res["method"].Value(), res["params"] as JObject, token)); + else + OnResponse(new MessageId(res["sessionId"]?.Value(), res["id"].Value()), Result.FromJson(res)); + } + + void ProcessIdeMessage(string msg, CancellationToken token) + { + Log("protocol", $"ide: {msg}"); + if (!string.IsNullOrEmpty(msg)) + { + var res = JObject.Parse(msg); + pending_ops.Add(OnCommand( + new MessageId(res["sessionId"]?.Value(), res["id"].Value()), + res["method"].Value(), + res["params"] as JObject, token)); + } + } + + internal async Task SendCommand(SessionId id, string method, JObject args, CancellationToken token) + { + //Log ("verbose", $"sending command {method}: {args}"); + return await SendCommandInternal(id, method, args, token); + } + + Task SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token) + { + int id = Interlocked.Increment(ref next_cmd_id); + + var o = JObject.FromObject(new + { + id, + method, + @params = args + }); + if (sessionId.sessionId != null) + o["sessionId"] = sessionId.sessionId; + var tcs = new TaskCompletionSource(); + + var msgId = new MessageId(sessionId.sessionId, id); + //Log ("verbose", $"add cmd id {sessionId}-{id}"); + pending_cmds[msgId] = tcs; + + Send(this.browser, o, token); + return tcs.Task; + } + + public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + { + //Log ("verbose", $"sending event {method}: {args}"); + SendEventInternal(sessionId, method, args, token); + } + + void SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token) + { + var o = JObject.FromObject(new + { + method, + @params = args + }); + if (sessionId.sessionId != null) + o["sessionId"] = sessionId.sessionId; + + Send(this.ide, o, token); + } + + internal void SendResponse(MessageId id, Result result, CancellationToken token) + { + SendResponseInternal(id, result, token); + } + + void SendResponseInternal(MessageId id, Result result, CancellationToken token) + { + JObject o = result.ToJObject(id); + if (result.IsErr) + logger.LogError($"sending error response for id: {id} -> {result}"); + + Send(this.ide, o, token); + } + + // , HttpContext context) + public async Task Run(Uri browserUri, WebSocket ideSocket) + { + Log("info", $"DevToolsProxy: Starting on {browserUri}"); + using (this.ide = ideSocket) + { + Log("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}"); + queues.Add(new DevToolsQueue(this.ide)); + using (this.browser = new ClientWebSocket()) + { + this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; + await this.browser.ConnectAsync(browserUri, CancellationToken.None); + queues.Add(new DevToolsQueue(this.browser)); + + Log("verbose", $"DevToolsProxy: Client connected on {browserUri}"); + var x = new CancellationTokenSource(); + + pending_ops.Add(ReadOne(browser, x.Token)); + pending_ops.Add(ReadOne(ide, x.Token)); + pending_ops.Add(side_exception.Task); + pending_ops.Add(client_initiated_close.Task); + + try + { + while (!x.IsCancellationRequested) + { + var task = await Task.WhenAny(pending_ops.ToArray()); + //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task)); + if (task == pending_ops[0]) + { + var msg = ((Task)task).Result; + if (msg != null) + { + pending_ops[0] = ReadOne(browser, x.Token); //queue next read + ProcessBrowserMessage(msg, x.Token); + } + } + else if (task == pending_ops[1]) + { + var msg = ((Task)task).Result; + if (msg != null) + { + pending_ops[1] = ReadOne(ide, x.Token); //queue next read + ProcessIdeMessage(msg, x.Token); + } + } + else if (task == pending_ops[2]) + { + var res = ((Task)task).Result; + throw new Exception("side task must always complete with an exception, what's going on???"); + } + else if (task == pending_ops[3]) + { + var res = ((Task)task).Result; + Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}"); + x.Cancel(); + } + else + { + //must be a background task + pending_ops.Remove(task); + var queue = GetQueueForTask(task); + if (queue != null) + { + var tsk = queue.Pump(x.Token); + if (tsk != null) + pending_ops.Add(tsk); + } + } + } + } + catch (Exception e) + { + Log("error", $"DevToolsProxy::Run: Exception {e}"); + //throw; + } + finally + { + if (!x.IsCancellationRequested) + x.Cancel(); + } + } + } + } + + protected void Log(string priority, string msg) + { + switch (priority) + { + case "protocol": + logger.LogTrace(msg); + break; + case "verbose": + logger.LogDebug(msg); + break; + case "info": + case "warning": + case "error": + default: + logger.LogDebug(msg); + break; + } + } + } +} diff --git a/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs b/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs new file mode 100644 index 000000000000..f863a3c8a892 --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs @@ -0,0 +1,352 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Emit; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + + internal class EvaluateExpression + { + class FindVariableNMethodCall : CSharpSyntaxWalker + { + public List identifiers = new List(); + public List methodCall = new List(); + public List memberAccesses = new List(); + public List argValues = new List(); + + public override void Visit(SyntaxNode node) + { + // TODO: PointerMemberAccessExpression + if (node is MemberAccessExpressionSyntax maes + && node.Kind() == SyntaxKind.SimpleMemberAccessExpression + && !(node.Parent is MemberAccessExpressionSyntax)) + { + memberAccesses.Add(maes); + } + + if (node is IdentifierNameSyntax identifier + && !(identifier.Parent is MemberAccessExpressionSyntax) + && !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text)) + { + identifiers.Add(identifier); + } + + if (node is InvocationExpressionSyntax) + { + methodCall.Add(node as InvocationExpressionSyntax); + throw new Exception("Method Call is not implemented yet"); + } + if (node is AssignmentExpressionSyntax) + throw new Exception("Assignment is not implemented yet"); + base.Visit(node); + } + + public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_values, IEnumerable id_values) + { + CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot(); + var memberAccessToParamName = new Dictionary(); + + // 1. Replace all this.a occurrences with this_a_ABDE + root = root.ReplaceNodes(memberAccesses, (maes, _) => + { + var ma_str = maes.ToString(); + if (!memberAccessToParamName.TryGetValue(ma_str, out var id_name)) + { + // Generate a random suffix + string suffix = Guid.NewGuid().ToString().Substring(0, 5); + string prefix = ma_str.Trim().Replace(".", "_"); + id_name = $"{prefix}_{suffix}"; + + memberAccessToParamName[ma_str] = id_name; + } + + return SyntaxFactory.IdentifierName(id_name); + }); + + var paramsSet = new HashSet(); + + // 2. For every unique member ref, add a corresponding method param + foreach (var (maes, value) in memberAccesses.Zip(ma_values)) + { + var node_str = maes.ToString(); + if (!memberAccessToParamName.TryGetValue(node_str, out var id_name)) + { + throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}"); + } + + root = UpdateWithNewMethodParam(root, id_name, value); + } + + foreach (var (idns, value) in identifiers.Zip(id_values)) + { + root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value); + } + + return syntaxTree.WithRootAndOptions(root, syntaxTree.Options); + + CompilationUnitSyntax UpdateWithNewMethodParam(CompilationUnitSyntax root, string id_name, JObject value) + { + var classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax; + var method = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax; + + if (paramsSet.Contains(id_name)) + { + // repeated member access expression + // eg. this.a + this.a + return root; + } + + argValues.Add(ConvertJSToCSharpType(value)); + + var updatedMethod = method.AddParameterListParameters( + SyntaxFactory.Parameter( + SyntaxFactory.Identifier(id_name)) + .WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value)))); + + paramsSet.Add(id_name); + root = root.ReplaceNode(method, updatedMethod); + + return root; + } + } + + private object ConvertJSToCSharpType(JToken variable) + { + var value = variable["value"]; + var type = variable["type"].Value(); + var subType = variable["subtype"]?.Value(); + + switch (type) + { + case "string": + return value?.Value(); + case "number": + return value?.Value(); + case "boolean": + return value?.Value(); + case "object": + if (subType == "null") + return null; + break; + } + throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported"); + } + + private string GetTypeFullName(JToken variable) + { + var type = variable["type"].ToString(); + var subType = variable["subtype"]?.Value(); + object value = ConvertJSToCSharpType(variable); + + switch (type) + { + case "object": + { + if (subType == "null") + return variable["className"].Value(); + break; + } + default: + return value.GetType().FullName; + } + throw new ReturnAsErrorException($"GetTypefullName: Evaluate of this datatype {type} not implemented yet", "Unsupported"); + } + } + + static SyntaxNode GetExpressionFromSyntaxTree(SyntaxTree syntaxTree) + { + CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot(); + ClassDeclarationSyntax classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax; + MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax; + BlockSyntax blockValue = methodDeclaration.Body; + ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt(0) as ReturnStatementSyntax; + ParenthesizedExpressionSyntax expressionParenthesized = returnValue.Expression as ParenthesizedExpressionSyntax; + + return expressionParenthesized?.Expression; + } + + private static async Task> ResolveMemberAccessExpressions(IEnumerable member_accesses, + MemberReferenceResolver resolver, CancellationToken token) + { + var memberAccessValues = new List(); + foreach (var maes in member_accesses) + { + var memberAccessString = maes.ToString(); + var value = await resolver.Resolve(memberAccessString, token); + if (value == null) + throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError"); + + memberAccessValues.Add(value); + } + + return memberAccessValues; + } + + private static async Task> ResolveIdentifiers(IEnumerable identifiers, MemberReferenceResolver resolver, CancellationToken token) + { + var values = new List(); + foreach (var var in identifiers) + { + JObject value = await resolver.Resolve(var.Identifier.Text, token); + if (value == null) + throw new ReturnAsErrorException($"The name {var.Identifier.Text} does not exist in the current context", "ReferenceError"); + + values.Add(value); + } + + return values; + } + + internal static async Task CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token) + { + expression = expression.Trim(); + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" + using System; + public class CompileAndRunTheExpression + { + public static object Evaluate() + { + return (" + expression + @"); + } + }"); + + var expressionTree = GetExpressionFromSyntaxTree(syntaxTree); + if (expressionTree == null) + throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); + + FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall(); + findVarNMethodCall.Visit(expressionTree); + + // this fails with `"a)"` + // because the code becomes: return (a)); + // and the returned expression from GetExpressionFromSyntaxTree is `a`! + if (expressionTree.Kind() == SyntaxKind.IdentifierName || expressionTree.Kind() == SyntaxKind.ThisExpression) + { + var var_name = expressionTree.ToString(); + var value = await resolver.Resolve(var_name, token); + if (value == null) + throw new ReturnAsErrorException($"Cannot find member named '{var_name}'.", "ReferenceError"); + + return value; + } + + var memberAccessValues = await ResolveMemberAccessExpressions(findVarNMethodCall.memberAccesses, resolver, token); + + // eg. "this.dateTime", " dateTime.TimeOfDay" + if (expressionTree.Kind() == SyntaxKind.SimpleMemberAccessExpression && findVarNMethodCall.memberAccesses.Count == 1) + { + return memberAccessValues[0]; + } + + var identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token); + + syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues); + expressionTree = GetExpressionFromSyntaxTree(syntaxTree); + if (expressionTree == null) + throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); + + MetadataReference[] references = new MetadataReference[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) + }; + + CSharpCompilation compilation = CSharpCompilation.Create( + "compileAndRunTheExpression", + syntaxTrees: new[] { syntaxTree }, + references: references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var semanticModel = compilation.GetSemanticModel(syntaxTree); + var typeInfo = semanticModel.GetTypeInfo(expressionTree); + + using (var ms = new MemoryStream()) + { + EmitResult result = compilation.Emit(ms); + if (!result.Success) + { + var sb = new StringBuilder(); + foreach (var d in result.Diagnostics) + sb.Append(d.ToString()); + + throw new ReturnAsErrorException(sb.ToString(), "CompilationError"); + } + + ms.Seek(0, SeekOrigin.Begin); + Assembly assembly = Assembly.Load(ms.ToArray()); + Type type = assembly.GetType("CompileAndRunTheExpression"); + + var ret = type.InvokeMember("Evaluate", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + findVarNMethodCall.argValues.ToArray()); + + return JObject.FromObject(ConvertCSharpToJSType(ret, typeInfo.Type)); + } + } + + static readonly HashSet NumericTypes = new HashSet + { + typeof(decimal), typeof(byte), typeof(sbyte), + typeof(short), typeof(ushort), + typeof(int), typeof(uint), + typeof(float), typeof(double) + }; + + static object ConvertCSharpToJSType(object v, ITypeSymbol type) + { + if (v == null) + return new { type = "object", subtype = "null", className = type.ToString() }; + + if (v is string s) + { + return new { type = "string", value = s, description = s }; + } + else if (NumericTypes.Contains(v.GetType())) + { + return new { type = "number", value = v, description = v.ToString() }; + } + else + { + return new { type = "object", value = v, description = v.ToString(), className = type.ToString() }; + } + } + + } + + class ReturnAsErrorException : Exception + { + public Result Error { get; } + public ReturnAsErrorException(JObject error) + => Error = Result.Err(error); + + public ReturnAsErrorException(string message, string className) + { + Error = Result.Err(JObject.FromObject(new + { + result = new + { + type = "object", + subtype = "error", + description = message, + className + } + })); + } + } +} diff --git a/sdks/wasm/BrowserDebugProxy/MemberReferenceResolver.cs b/sdks/wasm/BrowserDebugProxy/MemberReferenceResolver.cs new file mode 100644 index 000000000000..145e2237c5e2 --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/MemberReferenceResolver.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class MemberReferenceResolver + { + private MessageId messageId; + private int scopeId; + private MonoProxy proxy; + private ExecutionContext ctx; + private PerScopeCache scopeCache; + private VarInfo[] varIds; + private ILogger logger; + private bool locals_fetched = false; + + public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, MessageId msg_id, int scope_id, ILogger logger) + { + messageId = msg_id; + scopeId = scope_id; + this.proxy = proxy; + this.ctx = ctx; + this.logger = logger; + scopeCache = ctx.GetCacheForScope(scope_id); + } + + // Checks Locals, followed by `this` + public async Task Resolve(string var_name, CancellationToken token) + { + if (scopeCache.Locals.Count == 0 && !locals_fetched) + { + var scope_res = await proxy.GetScopeProperties(messageId, scopeId, token); + if (scope_res.IsErr) + throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}"); + locals_fetched = true; + } + + if (scopeCache.Locals.TryGetValue(var_name, out var obj)) + { + return obj["value"]?.Value(); + } + + if (scopeCache.MemberReferences.TryGetValue(var_name, out var ret)) + return ret; + + if (varIds == null) + { + var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId); + varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); + } + + var res = await proxy.SendMonoCommand(messageId, MonoCommands.EvaluateMemberAccess(scopeId, var_name, varIds), token); + if (res.IsOk) + { + ret = res.Value?["result"]?["value"]?["value"]?.Value(); + scopeCache.MemberReferences[var_name] = ret; + } + else + { + logger.LogDebug(res.Error.ToString()); + } + + return ret; + } + + } +} diff --git a/sdks/wasm/BrowserDebugProxy/MonoProxy.cs b/sdks/wasm/BrowserDebugProxy/MonoProxy.cs new file mode 100644 index 000000000000..1ecd6eec8d0a --- /dev/null +++ b/sdks/wasm/BrowserDebugProxy/MonoProxy.cs @@ -0,0 +1,977 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + + internal class MonoProxy : DevToolsProxy + { + HashSet sessions = new HashSet(); + Dictionary contexts = new Dictionary(); + + public MonoProxy(ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { this.hideWebDriver = hideWebDriver; } + + readonly bool hideWebDriver; + + internal ExecutionContext GetContext(SessionId sessionId) + { + if (contexts.TryGetValue(sessionId, out var context)) + return context; + + throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId)); + } + + bool UpdateContext(SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext) + { + var previous = contexts.TryGetValue(sessionId, out previousExecutionContext); + contexts[sessionId] = executionContext; + return previous; + } + + internal Task SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token); + + protected override async Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + { + switch (method) + { + case "Runtime.consoleAPICalled": + { + var type = args["type"]?.ToString(); + if (type == "debug") + { + var a = args["args"]; + if (a?[0]?["value"]?.ToString() == MonoConstants.RUNTIME_IS_READY && + a?[1]?["value"]?.ToString() == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28") + { + if (a.Count() > 2) + { + try + { + // The optional 3rd argument is the stringified assembly + // list so that we don't have to make more round trips + var context = GetContext(sessionId); + var loaded = a?[2]?["value"]?.ToString(); + if (loaded != null) + context.LoadedFiles = JToken.Parse(loaded).ToObject(); + } + catch (InvalidCastException ice) + { + Log("verbose", ice.ToString()); + } + } + await RuntimeReady(sessionId, token); + } + + } + break; + } + + case "Runtime.executionContextCreated": + { + SendEvent(sessionId, method, args, token); + var ctx = args?["context"]; + var aux_data = ctx?["auxData"] as JObject; + var id = ctx["id"].Value(); + if (aux_data != null) + { + var is_default = aux_data["isDefault"]?.Value(); + if (is_default == true) + { + await OnDefaultContext(sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token); + } + } + return true; + } + + case "Debugger.paused": + { + //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack + var top_func = args?["callFrames"]?[0]?["functionName"]?.Value(); + + if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_exception") + { + return await OnPause(sessionId, args, token); + } + break; + } + + case "Debugger.breakpointResolved": + { + break; + } + + case "Debugger.scriptParsed": + { + var url = args?["url"]?.Value() ?? ""; + + switch (url) + { + case var _ when url == "": + case var _ when url.StartsWith("wasm://", StringComparison.Ordinal): + { + Log("verbose", $"ignoring wasm: Debugger.scriptParsed {url}"); + return true; + } + } + Log("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}"); + break; + } + + case "Target.attachedToTarget": + { + if (args["targetInfo"]["type"]?.ToString() == "page") + await DeleteWebDriver(new SessionId(args["sessionId"]?.ToString()), token); + break; + } + + } + + return false; + } + + async Task IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token) + { + if (contexts.TryGetValue(sessionId, out var context) && context.IsRuntimeReady) + return true; + + var res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token); + return res.Value?["result"]?["value"]?.Value() ?? false; + } + + protected override async Task AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) + { + // Inspector doesn't use the Target domain or sessions + // so we try to init immediately + if (hideWebDriver && id == SessionId.Null) + await DeleteWebDriver(id, token); + + if (!contexts.TryGetValue(id, out var context)) + return false; + + switch (method) + { + case "Target.attachToTarget": + { + var resp = await SendCommand(id, method, args, token); + await DeleteWebDriver(new SessionId(resp.Value["sessionId"]?.ToString()), token); + break; + } + + case "Debugger.enable": + { + System.Console.WriteLine("recebi o Debugger.enable"); + var resp = await SendCommand(id, method, args, token); + + context.DebuggerId = resp.Value["debuggerId"]?.ToString(); + + if (await IsRuntimeAlreadyReadyAlready(id, token)) + await RuntimeReady(id, token); + + SendResponse(id, resp, token); + return true; + } + + case "Debugger.getScriptSource": + { + var script = args?["scriptId"]?.Value(); + return await OnGetScriptSource(id, script, token); + } + + case "Runtime.compileScript": + { + var exp = args?["expression"]?.Value(); + if (exp.StartsWith("//dotnet:", StringComparison.Ordinal)) + { + OnCompileDotnetScript(id, token); + return true; + } + break; + } + + case "Debugger.getPossibleBreakpoints": + { + var resp = await SendCommand(id, method, args, token); + if (resp.IsOk && resp.Value["locations"].HasValues) + { + SendResponse(id, resp, token); + return true; + } + + var start = SourceLocation.Parse(args?["start"] as JObject); + //FIXME support variant where restrictToFunction=true and end is omitted + var end = SourceLocation.Parse(args?["end"] as JObject); + if (start != null && end != null && await GetPossibleBreakpoints(id, start, end, token)) + return true; + + SendResponse(id, resp, token); + return true; + } + + case "Debugger.setBreakpoint": + { + break; + } + + case "Debugger.setBreakpointByUrl": + { + var resp = await SendCommand(id, method, args, token); + if (!resp.IsOk) + { + SendResponse(id, resp, token); + return true; + } + + var bpid = resp.Value["breakpointId"]?.ToString(); + var locations = resp.Value["locations"]?.Values(); + var request = BreakpointRequest.Parse(bpid, args); + + // is the store done loading? + var loaded = context.Source.Task.IsCompleted; + if (!loaded) + { + // Send and empty response immediately if not + // and register the breakpoint for resolution + context.BreakpointRequests[bpid] = request; + SendResponse(id, resp, token); + } + + if (await IsRuntimeAlreadyReadyAlready(id, token)) + { + var store = await RuntimeReady(id, token); + + Log("verbose", $"BP req {args}"); + await SetBreakpoint(id, store, request, !loaded, token); + } + + if (loaded) + { + // we were already loaded so we should send a response + // with the locations included and register the request + context.BreakpointRequests[bpid] = request; + var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations)); + SendResponse(id, result, token); + + } + return true; + } + + case "Debugger.removeBreakpoint": + { + await RemoveBreakpoint(id, args, token); + break; + } + + case "Debugger.resume": + { + await OnResume(id, token); + break; + } + + case "Debugger.stepInto": + { + return await Step(id, StepKind.Into, token); + } + + case "Debugger.stepOut": + { + return await Step(id, StepKind.Out, token); + } + + case "Debugger.stepOver": + { + return await Step(id, StepKind.Over, token); + } + + case "Debugger.evaluateOnCallFrame": + { + if (!DotnetObjectId.TryParse(args?["callFrameId"], out var objectId)) + return false; + + switch (objectId.Scheme) + { + case "scope": + return await OnEvaluateOnCallFrame(id, + int.Parse(objectId.Value), + args?["expression"]?.Value(), token); + default: + return false; + } + } + + case "Runtime.getProperties": + { + if (!DotnetObjectId.TryParse(args?["objectId"], out var objectId)) + break; + + var result = await RuntimeGetProperties(id, objectId, args, token); + SendResponse(id, result, token); + return true; + } + + case "Runtime.releaseObject": + { + if (!(DotnetObjectId.TryParse(args["objectId"], out var objectId) && objectId.Scheme == "cfo_res")) + break; + + await SendMonoCommand(id, MonoCommands.ReleaseObject(objectId), token); + SendResponse(id, Result.OkFromObject(new { }), token); + return true; + } + + case "Debugger.setPauseOnExceptions": + { + string state = args["state"].Value(); + await SendMonoCommand(id, MonoCommands.SetPauseOnExceptions(state), token); + // Pass this on to JS too + return false; + } + + // Protocol extensions + case "DotnetDebugger.getMethodLocation": + { + Console.WriteLine("set-breakpoint-by-method: " + id + " " + args); + + var store = await RuntimeReady(id, token); + string aname = args["assemblyName"]?.Value(); + string typeName = args["typeName"]?.Value(); + string methodName = args["methodName"]?.Value(); + if (aname == null || typeName == null || methodName == null) + { + SendResponse(id, Result.Err("Invalid protocol message '" + args + "'."), token); + return true; + } + + // GetAssemblyByName seems to work on file names + var assembly = store.GetAssemblyByName(aname); + if (assembly == null) + assembly = store.GetAssemblyByName(aname + ".exe"); + if (assembly == null) + assembly = store.GetAssemblyByName(aname + ".dll"); + if (assembly == null) + { + SendResponse(id, Result.Err("Assembly '" + aname + "' not found."), token); + return true; + } + + var type = assembly.GetTypeByName(typeName); + if (type == null) + { + SendResponse(id, Result.Err($"Type '{typeName}' not found."), token); + return true; + } + + var methodInfo = type.Methods.FirstOrDefault(m => m.Name == methodName); + if (methodInfo == null) + { + // Maybe this is an async method, in which case the debug info is attached + // to the async method implementation, in class named: + // `{type_name}/::MoveNext` + methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}/<{methodName}>"))? + .Methods.FirstOrDefault(mi => mi.Name == "MoveNext"); + } + + if (methodInfo == null) + { + SendResponse(id, Result.Err($"Method '{typeName}:{methodName}' not found."), token); + return true; + } + + var src_url = methodInfo.Assembly.Sources.Single(sf => sf.SourceId == methodInfo.SourceId).Url; + SendResponse(id, Result.OkFromObject(new + { + result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url } + }), token); + + return true; + } + case "Runtime.callFunctionOn": + { + if (!DotnetObjectId.TryParse(args["objectId"], out var objectId)) + return false; + + if (objectId.Scheme == "scope") + { + SendResponse(id, + Result.Exception(new ArgumentException( + $"Runtime.callFunctionOn not supported with scope ({objectId}).")), + token); + return true; + } + + var res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token); + var res_value_type = res.Value?["result"]?["value"]?.Type; + + if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object) + res = Result.OkFromObject(new { result = res.Value["result"]["value"] }); + + SendResponse(id, res, token); + return true; + } + } + + return false; + } + + async Task RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token) + { + if (objectId.Scheme == "scope") + { + return await GetScopeProperties(id, int.Parse(objectId.Value), token); + } + + var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token); + if (res.IsErr) + return res; + + if (objectId.Scheme == "cfo_res") + { + // Runtime.callFunctionOn result object + var value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); + if (value_json_str != null) + { + res = Result.OkFromObject(new + { + result = JArray.Parse(value_json_str) + }); + } + else + { + res = Result.OkFromObject(new { result = new { } }); + } + } + else + { + res = Result.Ok(JObject.FromObject(new { result = res.Value["result"]["value"] })); + } + + return res; + } + + //static int frame_id=0; + async Task OnPause(SessionId sessionId, JObject args, CancellationToken token) + { + //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime + var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token); + var orig_callframes = args?["callFrames"]?.Values(); + var context = GetContext(sessionId); + JObject data = null; + var reason = "other";//other means breakpoint + + if (res.IsErr) + { + //Give up and send the original call stack + return false; + } + + //step one, figure out where did we hit + var res_value = res.Value?["result"]?["value"]; + if (res_value == null || res_value is JValue) + { + //Give up and send the original call stack + return false; + } + + Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); + var bp_id = res_value?["breakpoint_id"]?.Value(); + Log("verbose", $"We just hit bp {bp_id}"); + if (!bp_id.HasValue) + { + //Give up and send the original call stack + return false; + } + + var bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == bp_id.Value); + + var callFrames = new List(); + foreach (var frame in orig_callframes) + { + var function_name = frame["functionName"]?.Value(); + var url = frame["url"]?.Value(); + if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name || + "_mono_wasm_fire_exception" == function_name) + { + if ("_mono_wasm_fire_exception" == function_name) + { + var exception_obj_id = await SendMonoCommand(sessionId, MonoCommands.GetExceptionObject(), token); + var res_val = exception_obj_id.Value?["result"]?["value"]; + var exception_dotnet_obj_id = new DotnetObjectId("object", res_val?["exception_id"]?.Value()); + data = JObject.FromObject(new + { + type = "object", + subtype = "error", + className = res_val?["class_name"]?.Value(), + uncaught = res_val?["uncaught"]?.Value(), + description = res_val?["message"]?.Value() + "\n", + objectId = exception_dotnet_obj_id.ToString() + }); + reason = "exception"; + } + + var frames = new List(); + int frame_id = 0; + var the_mono_frames = res.Value?["result"]?["value"]?["frames"]?.Values(); + + foreach (var mono_frame in the_mono_frames) + { + ++frame_id; + var il_pos = mono_frame["il_pos"].Value(); + var method_token = mono_frame["method_token"].Value(); + var assembly_name = mono_frame["assembly_name"].Value(); + + // This can be different than `method.Name`, like in case of generic methods + var method_name = mono_frame["method_name"]?.Value(); + + var store = await LoadStore(sessionId, token); + var asm = store.GetAssemblyByName(assembly_name); + if (asm == null) + { + Log("info", $"Unable to find assembly: {assembly_name}"); + continue; + } + + var method = asm.GetMethodByToken(method_token); + + if (method == null) + { + Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); + continue; + } + + var location = method?.GetLocationByIl(il_pos); + + // When hitting a breakpoint on the "IncrementCount" method in the standard + // Blazor project template, one of the stack frames is inside mscorlib.dll + // and we get location==null for it. It will trigger a NullReferenceException + // if we don't skip over that stack frame. + if (location == null) + { + continue; + } + + Log("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); + Log("info", $"\tmethod {method_name} location: {location}"); + frames.Add(new Frame(method, location, frame_id - 1)); + + callFrames.Add(new + { + functionName = method_name, + callFrameId = $"dotnet:scope:{frame_id - 1}", + functionLocation = method.StartLocation.AsLocation(), + + location = location.AsLocation(), + + url = store.ToUrl(location), + + scopeChain = new[] + { + new + { + type = "local", + @object = new + { + @type = "object", + className = "Object", + description = "Object", + objectId = $"dotnet:scope:{frame_id-1}", + }, + name = method_name, + startLocation = method.StartLocation.AsLocation(), + endLocation = method.EndLocation.AsLocation(), + } + } + }); + + context.CallStack = frames; + + } + } + else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || + url.StartsWith("wasm://wasm/", StringComparison.Ordinal))) + { + callFrames.Add(frame); + } + } + + var bp_list = new string[bp == null ? 0 : 1]; + if (bp != null) + bp_list[0] = bp.StackId; + + var o = JObject.FromObject(new + { + callFrames, + reason, + data, + hitBreakpoints = bp_list, + }); + + SendEvent(sessionId, "Debugger.paused", o, token); + return true; + } + + async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token) + { + Log("verbose", "Default context created, clearing state and sending events"); + if (UpdateContext(sessionId, context, out var previousContext)) + { + foreach (var kvp in previousContext.BreakpointRequests) + { + context.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); + } + } + + if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) + await RuntimeReady(sessionId, token); + } + + async Task OnResume(MessageId msg_id, CancellationToken token) + { + var ctx = GetContext(msg_id); + if (ctx.CallStack != null) + { + // Stopped on managed code + await SendMonoCommand(msg_id, MonoCommands.Resume(), token); + } + + //discard managed frames + GetContext(msg_id).ClearState(); + } + + async Task Step(MessageId msg_id, StepKind kind, CancellationToken token) + { + var context = GetContext(msg_id); + if (context.CallStack == null) + return false; + + if (context.CallStack.Count <= 1 && kind == StepKind.Out) + return false; + + var res = await SendMonoCommand(msg_id, MonoCommands.StartSingleStepping(kind), token); + + var ret_code = res.Value?["result"]?["value"]?.Value(); + + if (ret_code.HasValue && ret_code.Value == 0) + { + context.ClearState(); + await SendCommand(msg_id, "Debugger.stepOut", new JObject(), token); + return false; + } + + SendResponse(msg_id, Result.Ok(new JObject()), token); + + context.ClearState(); + + await SendCommand(msg_id, "Debugger.resume", new JObject(), token); + return true; + } + + async Task OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token) + { + try + { + var context = GetContext(msg_id); + if (context.CallStack == null) + return false; + + var resolver = new MemberReferenceResolver(this, context, msg_id, scope_id, logger); + + JObject retValue = await resolver.Resolve(expression, token); + if (retValue == null) + { + retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token); + } + + if (retValue != null) + { + SendResponse(msg_id, Result.OkFromObject(new + { + result = retValue + }), token); + } + else + { + SendResponse(msg_id, Result.Err($"Unable to evaluate '{expression}'"), token); + } + } + catch (ReturnAsErrorException ree) + { + SendResponse(msg_id, ree.Error, token); + } + catch (Exception e) + { + logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}."); + SendResponse(msg_id, Result.Exception(e), token); + } + + return true; + } + + internal async Task GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token) + { + try + { + var ctx = GetContext(msg_id); + var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scope_id); + if (scope == null) + return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scope_id}" })); + + var var_ids = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); + var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, var_ids), token); + + //if we fail we just buble that to the IDE (and let it panic over it) + if (res.IsErr) + return res; + + var values = res.Value?["result"]?["value"]?.Values().ToArray(); + + if (values == null || values.Length == 0) + return Result.OkFromObject(new { result = Array.Empty() }); + + var frameCache = ctx.GetCacheForScope(scope_id); + foreach (var value in values) + { + frameCache.Locals[value["name"]?.Value()] = value; + } + + return Result.OkFromObject(new { result = values }); + } + catch (Exception exception) + { + Log("verbose", $"Error resolving scope properties {exception.Message}"); + return Result.Exception(exception); + } + } + + async Task SetMonoBreakpoint(SessionId sessionId, string reqId, SourceLocation location, CancellationToken token) + { + var bp = new Breakpoint(reqId, location, BreakpointState.Pending); + var asm_name = bp.Location.CliLocation.Method.Assembly.Name; + var method_token = bp.Location.CliLocation.Method.Token; + var il_offset = bp.Location.CliLocation.Offset; + + var res = await SendMonoCommand(sessionId, MonoCommands.SetBreakpoint(asm_name, method_token, il_offset), token); + var ret_code = res.Value?["result"]?["value"]?.Value(); + + if (ret_code.HasValue) + { + bp.RemoteId = ret_code.Value; + bp.State = BreakpointState.Active; + //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); + } + + return bp; + } + + async Task LoadStore(SessionId sessionId, CancellationToken token) + { + var context = GetContext(sessionId); + + if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null) + return await context.Source.Task; + + try + { + var loaded_files = context.LoadedFiles; + + if (loaded_files == null) + { + var loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(), token); + loaded_files = loaded.Value?["result"]?["value"]?.ToObject(); + } + + await + foreach (var source in context.store.Load(sessionId, loaded_files, token).WithCancellation(token)) + { + var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); + Log("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}"); + + SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); + + foreach (var req in context.BreakpointRequests.Values) + { + if (req.TryResolve(source)) + { + await SetBreakpoint(sessionId, context.store, req, true, token); + } + } + } + } + catch (Exception e) + { + context.Source.SetException(e); + } + + if (!context.Source.Task.IsCompleted) + context.Source.SetResult(context.store); + return context.store; + } + + async Task RuntimeReady(SessionId sessionId, CancellationToken token) + { + var context = GetContext(sessionId); + if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) + return await context.ready.Task; + + var clear_result = await SendMonoCommand(sessionId, MonoCommands.ClearAllBreakpoints(), token); + if (clear_result.IsErr) + { + Log("verbose", $"Failed to clear breakpoints due to {clear_result}"); + } + + var store = await LoadStore(sessionId, token); + + context.ready.SetResult(store); + SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); + return store; + } + + async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) + { + var bpid = args?["breakpointId"]?.Value(); + + var context = GetContext(msg_id); + if (!context.BreakpointRequests.TryGetValue(bpid, out var breakpointRequest)) + return; + + foreach (var bp in breakpointRequest.Locations) + { + var res = await SendMonoCommand(msg_id, MonoCommands.RemoveBreakpoint(bp.RemoteId), token); + var ret_code = res.Value?["result"]?["value"]?.Value(); + + if (ret_code.HasValue) + { + bp.RemoteId = -1; + bp.State = BreakpointState.Disabled; + } + } + breakpointRequest.Locations.Clear(); + } + + async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token) + { + var context = GetContext(sessionId); + if (req.Locations.Any()) + { + Log("debug", $"locations already loaded for {req.Id}"); + return; + } + + var comparer = new SourceLocation.LocationComparer(); + // if column is specified the frontend wants the exact matches + // and will clear the bp if it isn't close enoug + var locations = store.FindBreakpointLocations(req) + .Distinct(comparer) + .Where(l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column)) + .OrderBy(l => l.Column) + .GroupBy(l => l.Id); + + logger.LogDebug("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext(sessionId).IsRuntimeReady); + + var breakpoints = new List(); + + foreach (var sourceId in locations) + { + var loc = sourceId.First(); + var bp = await SetMonoBreakpoint(sessionId, req.Id, loc, token); + + // If we didn't successfully enable the breakpoint + // don't add it to the list of locations for this id + if (bp.State != BreakpointState.Active) + continue; + + breakpoints.Add(bp); + + var resolvedLocation = new + { + breakpointId = req.Id, + location = loc.AsLocation() + }; + + if (sendResolvedEvent) + SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token); + } + + req.Locations.AddRange(breakpoints); + return; + } + + async Task GetPossibleBreakpoints(MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token) + { + var bps = (await RuntimeReady(msg, token)).FindPossibleBreakpoints(start, end); + + if (bps == null) + return false; + + var response = new { locations = bps.Select(b => b.AsLocation()) }; + + SendResponse(msg, Result.OkFromObject(response), token); + return true; + } + + void OnCompileDotnetScript(MessageId msg_id, CancellationToken token) + { + SendResponse(msg_id, Result.OkFromObject(new { }), token); + } + + async Task OnGetScriptSource(MessageId msg_id, string script_id, CancellationToken token) + { + if (!SourceId.TryParse(script_id, out var id)) + return false; + + var src_file = (await LoadStore(msg_id, token)).GetFileById(id); + + try + { + var uri = new Uri(src_file.Url); + string source = $"// Unable to find document {src_file.SourceUri}"; + + using (var data = await src_file.GetSourceAsync(checkHash: false, token: token)) + { + if (data.Length == 0) + return false; + + using (var reader = new StreamReader(data)) + source = await reader.ReadToEndAsync(); + } + SendResponse(msg_id, Result.OkFromObject(new { scriptSource = source }), token); + } + catch (Exception e) + { + var o = new + { + scriptSource = $"// Unable to read document ({e.Message})\n" + + $"Local path: {src_file?.SourceUri}\n" + + $"SourceLink path: {src_file?.SourceLinkUri}\n" + }; + + SendResponse(msg_id, Result.OkFromObject(o), token); + } + return true; + } + + async Task DeleteWebDriver(SessionId sessionId, CancellationToken token) + { + // see https://github.com/mono/mono/issues/19549 for background + if (hideWebDriver && sessions.Add(sessionId)) + { + var res = await SendCommand(sessionId, + "Page.addScriptToEvaluateOnNewDocument", + JObject.FromObject(new { source = "delete navigator.constructor.prototype.webdriver" }), + token); + + if (sessionId != SessionId.Null && !res.IsOk) + sessions.Remove(sessionId); + } + } + } +} diff --git a/sdks/wasm/DebuggerTestSuite/ArrayTests.cs b/sdks/wasm/DebuggerTestSuite/ArrayTests.cs index 0e010d4805a8..6e5e4a948dd4 100644 --- a/sdks/wasm/DebuggerTestSuite/ArrayTests.cs +++ b/sdks/wasm/DebuggerTestSuite/ArrayTests.cs @@ -1,556 +1,695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; namespace DebuggerTests { - public class ArrayTests : DebuggerTestBase { - - [Theory] - [InlineData (16, 2, "PrimitiveTypeLocals", false, 0, false)] - [InlineData (16, 2, "PrimitiveTypeLocals", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectPrimitiveTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:PrimitiveTypeLocals", - method_name: method_name, - etype_name: "int", - local_var_name_prefix: "int", - array: new [] { TNumber (4), TNumber (70), TNumber (1) }, - array_elements: null, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - [Theory] - [InlineData (32, 2, "ValueTypeLocals", false, 0, false)] - [InlineData (32, 2, "ValueTypeLocals", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectValueTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocals", - method_name: method_name, - etype_name: "DebuggerTests.Point", - local_var_name_prefix: "point", - array: new [] { - TValueType ("DebuggerTests.Point"), - TValueType ("DebuggerTests.Point"), - }, - array_elements: new [] { - TPoint (5, -2, "point_arr#Id#0", "Green"), - TPoint (123, 0, "point_arr#Id#1", "Blue") - }, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - [Theory] - [InlineData (49, 2, "ObjectTypeLocals", false, 0, false)] - [InlineData (49, 2, "ObjectTypeLocals", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectObjectArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectTypeLocals", - method_name: method_name, - etype_name: "DebuggerTests.SimpleClass", - local_var_name_prefix: "class", - array: new [] { - TObject ("DebuggerTests.SimpleClass"), - TObject ("DebuggerTests.SimpleClass", is_null: true), - TObject ("DebuggerTests.SimpleClass") - }, - array_elements: new [] { - TSimpleClass (5, -2, "class_arr#Id#0", "Green"), - null, // Element is null - TSimpleClass (123, 0, "class_arr#Id#2", "Blue") }, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - [Theory] - [InlineData (66, 2, "GenericTypeLocals", false, 0, false)] - [InlineData (66, 2, "GenericTypeLocals", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectGenericTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericTypeLocals", - method_name: method_name, - etype_name: "DebuggerTests.GenericClass", - local_var_name_prefix: "gclass", - array: new [] { - TObject ("DebuggerTests.GenericClass", is_null: true), - TObject ("DebuggerTests.GenericClass"), - TObject ("DebuggerTests.GenericClass") - }, - array_elements: new [] { - null, // Element is null - new { - Id = TString ("gclass_arr#1#Id"), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TNumber (5) - }, - new { - Id = TString ("gclass_arr#2#Id"), - Color = TEnum ("DebuggerTests.RGB", "Blue"), - Value = TNumber (-12) - } - }, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - [Theory] - [InlineData (82, 2, "GenericValueTypeLocals", false, 0, false)] - [InlineData (82, 2, "GenericValueTypeLocals", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectGenericValueTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals", - method_name: method_name, - etype_name: "DebuggerTests.SimpleGenericStruct", - local_var_name_prefix: "gvclass", - array: new [] { - TValueType ("DebuggerTests.SimpleGenericStruct"), - TValueType ("DebuggerTests.SimpleGenericStruct") - }, - array_elements: new [] { - new { - Id = TString ("gvclass_arr#1#Id"), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red") - }, - new { - Id = TString ("gvclass_arr#2#Id"), - Color = TEnum ("DebuggerTests.RGB", "Blue"), - Value = TPoint (10, 20, "gvclass_arr#2#Value#Id", "Green") - } - }, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - [Theory] - [InlineData (191, 2, "GenericValueTypeLocals2", false, 0, false)] - [InlineData (191, 2, "GenericValueTypeLocals2", false, 0, true)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, false)] - [InlineData (93, 2, "YetAnotherMethod", true, 2, true)] - public async Task InspectGenericValueTypeArrayLocals2 (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) - => await TestSimpleArrayLocals ( - line, col, - entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals2", - method_name: method_name, - etype_name: "DebuggerTests.SimpleGenericStruct", - local_var_name_prefix: "gvclass", - array: new [] { - TValueType ("DebuggerTests.SimpleGenericStruct"), - TValueType ("DebuggerTests.SimpleGenericStruct") - }, - array_elements: new [] { - new { - Id = TString ("gvclass_arr#0#Id"), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = new [] { - TPoint (100, 200, "gvclass_arr#0#0#Value#Id", "Red"), - TPoint (100, 200, "gvclass_arr#0#1#Value#Id", "Green") - } - }, - new { - Id = TString ("gvclass_arr#1#Id"), - Color = TEnum ("DebuggerTests.RGB", "Blue"), - Value = new [] { - TPoint (100, 200, "gvclass_arr#1#0#Value#Id", "Green"), - TPoint (100, 200, "gvclass_arr#1#1#Value#Id", "Blue") - } - } - }, - test_prev_frame: test_prev_frame, - frame_idx: frame_idx, - use_cfo: use_cfo); - - async Task TestSimpleArrayLocals (int line, int col, string entry_method_name, string method_name, string etype_name, - string local_var_name_prefix, object[] array, object[] array_elements, - bool test_prev_frame=false, int frame_idx=0, bool use_cfo = false) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() { invoke_static_method (" - + $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" - + "); }, 1);"; - - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name); - - var locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - Assert.Equal (4, locals.Count ()); - CheckArray (locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]"); - CheckArray (locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]"); - CheckObject (locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); - CheckBool (locals, "call_other", test_prev_frame); - - var local_arr_name = $"{local_var_name_prefix}_arr"; - - JToken prefix_arr; - if (use_cfo) { // Use `Runtime.callFunctionOn` to get the properties - var frame = pause_location ["callFrames"][frame_idx]; - var name = local_arr_name; - var fl = await GetProperties (frame ["callFrameId"].Value ()); - var l_obj = GetAndAssertObjectWithName (locals, name); - var l_objectId = l_obj ["value"]["objectId"]?.Value (); - - Assert.True (!String.IsNullOrEmpty (l_objectId), $"No objectId found for {name}"); - - prefix_arr = await GetObjectWithCFO (l_objectId); - } else { - prefix_arr = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], local_arr_name); - } - - await CheckProps (prefix_arr, array, local_arr_name); - - if (array_elements?.Length > 0) { - for (int i = 0; i < array_elements.Length; i ++) { - var i_str = i.ToString (); - var label = $"{local_var_name_prefix}_arr[{i}]"; - if (array_elements [i] == null) { - var act_i = prefix_arr.FirstOrDefault (jt => jt ["name"]?.Value () == i_str); - Assert.True (act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); - - await CheckValue (act_i ["value"], TObject (etype_name, is_null: true), label); - } else { - await CompareObjectPropertiesFor (prefix_arr, i_str, array_elements [i], label: label); - } - } - } - - var props = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); - await CheckProps (props, new object[0], "${local_var_name_prefix}_arr_empty"); - }); - - async Task GetObjectWithCFO (string objectId, JObject fn_args = null) - { - var fn_decl = "function () { return this; }"; - var cfo_args = JObject.FromObject (new { - functionDeclaration = fn_decl, - objectId = objectId - }); - - if (fn_args != null) - cfo_args ["arguments"] = fn_args; - - // callFunctionOn - var result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - - return await GetProperties (result.Value ["result"]["objectId"]?.Value (), fn_args); - } - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectObjectArrayMembers (bool use_cfo) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - int line = 205; - int col = 3; - string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers"; - string method_name = "PlaceholderMethod"; - int frame_idx = 1; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() { invoke_static_method (" - + $"'{entry_method_name}'" - + "); }, 1);"; - - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name); - var locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - Assert.Single (locals); - CheckObject (locals, "c", "DebuggerTests.Container"); - - var c_props = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], "c"); - await CheckProps (c_props, new { - id = TString ("c#id"), - ClassArrayProperty = TArray ("DebuggerTests.SimpleClass[]", 3), - ClassArrayField = TArray ("DebuggerTests.SimpleClass[]", 3), - PointsProperty = TArray ("DebuggerTests.Point[]", 2), - PointsField = TArray ("DebuggerTests.Point[]", 2) - }, - "c" - ); - - await CompareObjectPropertiesFor (c_props, "ClassArrayProperty", - new [] { - TSimpleClass (5, -2, "ClassArrayProperty#Id#0", "Green"), - TSimpleClass (30, 1293, "ClassArrayProperty#Id#1", "Green"), - TObject ("DebuggerTests.SimpleClass", is_null: true) - }, - label: "InspectLocalsWithStructsStaticAsync"); - - await CompareObjectPropertiesFor (c_props, "ClassArrayField", - new [] { - TObject ("DebuggerTests.SimpleClass", is_null: true), - TSimpleClass (5, -2, "ClassArrayField#Id#1", "Blue"), - TSimpleClass (30, 1293, "ClassArrayField#Id#2", "Green") - }, - label: "c#ClassArrayField"); - - await CompareObjectPropertiesFor (c_props, "PointsProperty", - new [] { - TPoint (5, -2, "PointsProperty#Id#0", "Green"), - TPoint (123, 0, "PointsProperty#Id#1", "Blue"), - }, - label: "c#PointsProperty"); - - await CompareObjectPropertiesFor (c_props, "PointsField", - new [] { - TPoint (5, -2, "PointsField#Id#0", "Green"), - TPoint (123, 0, "PointsField#Id#1", "Blue"), - }, - label: "c#PointsField"); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectValueTypeArrayLocalsStaticAsync (bool use_cfo) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - int line = 143; - int col = 3; - string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; - string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync - int frame_idx = 0; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" - + $"'{entry_method_name}', false" // *false* here keeps us only in the static method - + "); }, 1);"; - - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name); - var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - await CheckProps (frame_locals, new { - call_other = TBool (false), - gvclass_arr = TArray ("DebuggerTests.SimpleGenericStruct[]", 2), - gvclass_arr_empty = TArray ("DebuggerTests.SimpleGenericStruct[]"), - gvclass_arr_null = TObject ("DebuggerTests.SimpleGenericStruct[]", is_null: true), - gvclass = TValueType ("DebuggerTests.SimpleGenericStruct"), - // BUG: this shouldn't be null! - points = TObject ("DebuggerTests.Point[]", is_null: true) - }, "ValueTypeLocalsAsync#locals"); - - var local_var_name_prefix = "gvclass"; - await CompareObjectPropertiesFor (frame_locals, local_var_name_prefix, new { - Id = TString (null), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TPoint (0, 0, null, "Red") - }); - - await CompareObjectPropertiesFor (frame_locals, $"{local_var_name_prefix}_arr", - new [] { - new { - Id = TString ("gvclass_arr#1#Id"), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red") - }, - new { - Id = TString ("gvclass_arr#2#Id"), - Color = TEnum ("DebuggerTests.RGB", "Blue"), - Value = TPoint (10, 20, "gvclass_arr#2#Value#Id", "Green") - } - } - ); - await CompareObjectPropertiesFor (frame_locals, $"{local_var_name_prefix}_arr_empty", - new object[0]); - }); - } - - // TODO: Check previous frame too - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectValueTypeArrayLocalsInstanceAsync (bool use_cfo) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - int line = 155; - int col = 3; - string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; - int frame_idx = 0; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" - + $"'{entry_method_name}', true" - + "); }, 1);"; - - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext"); - - var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - await CheckProps (frame_locals, new { - t1 = TObject ("DebuggerTests.SimpleGenericStruct"), - @this = TObject ("DebuggerTests.ArrayTestsClass"), - point_arr = TArray ("DebuggerTests.Point[]", 2), - point = TValueType ("DebuggerTests.Point") - }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); - - await CompareObjectPropertiesFor (frame_locals, "t1", - new { - Id = TString ("gvclass_arr#1#Id"), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red") - }); - - await CompareObjectPropertiesFor (frame_locals, "point_arr", - new [] { - TPoint (5, -2, "point_arr#Id#0", "Red"), - TPoint (123, 0, "point_arr#Id#1", "Blue"), - } - ); - - await CompareObjectPropertiesFor (frame_locals, "point", - TPoint (45, 51, "point#Id", "Green")); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod (bool use_cfo) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - int line = 222; - int col = 3; - string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; - int frame_idx = 0; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - //await SetBreakpoint (debugger_test_loc, 143, 3); - - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" - + $"'{entry_method_name}', false" - + "); }, 1);"; - - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext"); - - var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - await CheckProps (frame_locals, new { - call_other = TBool (false), - local_i = TNumber (5), - sc = TSimpleClass (10, 45, "sc#Id", "Blue") - }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod (bool use_cfo) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - int line = 229; - int col = 3; - string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; - int frame_idx = 0; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" - + $"'{entry_method_name}', true" - + "); }, 1);"; - - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext"); - - var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - await CheckProps (frame_locals, new { - sc_arg = TObject ("DebuggerTests.SimpleClass"), - @this = TValueType ("DebuggerTests.Point"), - local_gs = TValueType ("DebuggerTests.SimpleGenericStruct") - }, - "locals#0"); - - await CompareObjectPropertiesFor (frame_locals, "local_gs", - new { - Id = TString ("local_gs#Id"), - Color = TEnum ("DebuggerTests.RGB", "Green"), - Value = TNumber (4) - }, - label: "local_gs#0"); - - await CompareObjectPropertiesFor (frame_locals, "sc_arg", - TSimpleClass (10, 45, "sc_arg#Id", "Blue"), - label: "sc_arg#0"); - - await CompareObjectPropertiesFor (frame_locals, "this", - TPoint (90, -4, "point#Id", "Green"), - label: "this#0"); - }); - } - - } + public class ArrayTests : DebuggerTestBase + { + + [Theory] + [InlineData(19, 8, "PrimitiveTypeLocals", false, 0, false)] + [InlineData(19, 8, "PrimitiveTypeLocals", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectPrimitiveTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:PrimitiveTypeLocals", + method_name: method_name, + etype_name: "int", + local_var_name_prefix: "int", + array: new[] { TNumber(4), TNumber(70), TNumber(1) }, + array_elem_props: null, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + [Theory] + [InlineData(36, 8, "ValueTypeLocals", false, 0, false)] + [InlineData(36, 8, "ValueTypeLocals", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectValueTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocals", + method_name: method_name, + etype_name: "DebuggerTests.Point", + local_var_name_prefix: "point", + array: new[] + { + TValueType("DebuggerTests.Point"), + TValueType("DebuggerTests.Point"), + }, + array_elem_props: new[] + { + TPoint(5, -2, "point_arr#Id#0", "Green"), + TPoint(123, 0, "point_arr#Id#1", "Blue") + }, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + [Theory] + [InlineData(54, 8, "ObjectTypeLocals", false, 0, false)] + [InlineData(54, 8, "ObjectTypeLocals", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectObjectArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectTypeLocals", + method_name: method_name, + etype_name: "DebuggerTests.SimpleClass", + local_var_name_prefix: "class", + array: new[] + { + TObject("DebuggerTests.SimpleClass"), + TObject("DebuggerTests.SimpleClass", is_null : true), + TObject("DebuggerTests.SimpleClass") + }, + array_elem_props: new[] + { + TSimpleClass(5, -2, "class_arr#Id#0", "Green"), + null, // Element is null + TSimpleClass(123, 0, "class_arr#Id#2", "Blue") + }, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + [Theory] + [InlineData(72, 8, "GenericTypeLocals", false, 0, false)] + [InlineData(72, 8, "GenericTypeLocals", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectGenericTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericTypeLocals", + method_name: method_name, + etype_name: "DebuggerTests.GenericClass", + local_var_name_prefix: "gclass", + array: new[] + { + TObject("DebuggerTests.GenericClass", is_null : true), + TObject("DebuggerTests.GenericClass"), + TObject("DebuggerTests.GenericClass") + }, + array_elem_props: new[] + { + null, // Element is null + new + { + Id = TString("gclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TNumber(5) + }, + new + { + Id = TString("gclass_arr#2#Id"), + Color = TEnum("DebuggerTests.RGB", "Blue"), + Value = TNumber(-12) + } + }, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + [Theory] + [InlineData(89, 8, "GenericValueTypeLocals", false, 0, false)] + [InlineData(89, 8, "GenericValueTypeLocals", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectGenericValueTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals", + method_name: method_name, + etype_name: "DebuggerTests.SimpleGenericStruct", + local_var_name_prefix: "gvclass", + array: new[] + { + TValueType("DebuggerTests.SimpleGenericStruct"), + TValueType("DebuggerTests.SimpleGenericStruct") + }, + array_elem_props: new[] + { + new + { + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") + }, + new + { + Id = TString("gvclass_arr#2#Id"), + Color = TEnum("DebuggerTests.RGB", "Blue"), + Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green") + } + }, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + [Theory] + [InlineData(213, 8, "GenericValueTypeLocals2", false, 0, false)] + [InlineData(213, 8, "GenericValueTypeLocals2", false, 0, true)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, false)] + [InlineData(100, 8, "YetAnotherMethod", true, 2, true)] + public async Task InspectGenericValueTypeArrayLocals2(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals( + line, col, + entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals2", + method_name: method_name, + etype_name: "DebuggerTests.SimpleGenericStruct", + local_var_name_prefix: "gvclass", + array: new[] + { + TValueType("DebuggerTests.SimpleGenericStruct"), + TValueType("DebuggerTests.SimpleGenericStruct") + }, + array_elem_props: new[] + { + new + { + Id = TString("gvclass_arr#0#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = new [] + { + TPoint(100, 200, "gvclass_arr#0#0#Value#Id", "Red"), + TPoint(100, 200, "gvclass_arr#0#1#Value#Id", "Green") + } + }, + new + { + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Blue"), + Value = new [] + { + TPoint(100, 200, "gvclass_arr#1#0#Value#Id", "Green"), + TPoint(100, 200, "gvclass_arr#1#1#Value#Id", "Blue") + } + } + }, + test_prev_frame: test_prev_frame, + frame_idx: frame_idx, + use_cfo: use_cfo); + + async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, string method_name, string etype_name, + string local_var_name_prefix, object[] array, object[] array_elem_props, + bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Equal(4, locals.Count()); + CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0); + CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0); + CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); + CheckBool(locals, "call_other", test_prev_frame); + + var local_arr_name = $"{local_var_name_prefix}_arr"; + + JToken prefix_arr; + if (use_cfo) + { // Use `Runtime.callFunctionOn` to get the properties + var frame = pause_location["callFrames"][frame_idx]; + var name = local_arr_name; + var fl = await GetProperties(frame["callFrameId"].Value()); + var l_obj = GetAndAssertObjectWithName(locals, name); + var l_objectId = l_obj["value"]["objectId"]?.Value(); + + Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}"); + + prefix_arr = await GetObjectWithCFO(l_objectId); + } + else + { + prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name); + } + + await CheckProps(prefix_arr, array, local_arr_name); + + if (array_elem_props?.Length > 0) + { + for (int i = 0; i < array_elem_props.Length; i++) + { + var i_str = i.ToString(); + var label = $"{local_var_name_prefix}_arr[{i}]"; + if (array_elem_props[i] == null) + { + var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value() == i_str); + Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); + + await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label); + } + else + { + await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label); + } + } + } + + var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); + await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty"); + }); + + async Task GetObjectWithCFO(string objectId, JObject fn_args = null) + { + var fn_decl = "function () { return this; }"; + var cfo_args = JObject.FromObject(new + { + functionDeclaration = fn_decl, + objectId = objectId + }); + + if (fn_args != null) + cfo_args["arguments"] = fn_args; + + // callFunctionOn + var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + + return await GetProperties(result.Value["result"]["objectId"]?.Value(), fn_args); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectObjectArrayMembers(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 227; + int col = 12; + string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers"; + string method_name = "PlaceholderMethod"; + int frame_idx = 1; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Single(locals); + CheckObject(locals, "c", "DebuggerTests.Container"); + + var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c"); + await CheckProps(c_props, new + { + id = TString("c#id"), + ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3), + ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3), + PointsProperty = TArray("DebuggerTests.Point[]", 2), + PointsField = TArray("DebuggerTests.Point[]", 2) + }, + "c" + ); + + await CompareObjectPropertiesFor(c_props, "ClassArrayProperty", + new[] + { + TSimpleClass(5, -2, "ClassArrayProperty#Id#0", "Green"), + TSimpleClass(30, 1293, "ClassArrayProperty#Id#1", "Green"), + TObject("DebuggerTests.SimpleClass", is_null : true) + }, + label: "InspectLocalsWithStructsStaticAsync"); + + await CompareObjectPropertiesFor(c_props, "ClassArrayField", + new[] + { + TObject("DebuggerTests.SimpleClass", is_null : true), + TSimpleClass(5, -2, "ClassArrayField#Id#1", "Blue"), + TSimpleClass(30, 1293, "ClassArrayField#Id#2", "Green") + }, + label: "c#ClassArrayField"); + + await CompareObjectPropertiesFor(c_props, "PointsProperty", + new[] + { + TPoint(5, -2, "PointsProperty#Id#0", "Green"), + TPoint(123, 0, "PointsProperty#Id#1", "Blue"), + }, + label: "c#PointsProperty"); + + await CompareObjectPropertiesFor(c_props, "PointsField", + new[] + { + TPoint(5, -2, "PointsField#Id#0", "Green"), + TPoint(123, 0, "PointsField#Id#1", "Blue"), + }, + label: "c#PointsField"); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 157; + int col = 12; + string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; + string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync + int frame_idx = 0; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" // *false* here keeps us only in the static method + + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), + gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), + gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), + gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), + // BUG: this shouldn't be null! + points = TObject("DebuggerTests.Point[]", is_null: true) + }, "ValueTypeLocalsAsync#locals"); + + var local_var_name_prefix = "gvclass"; + await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new + { + Id = TString(null), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(0, 0, null, "Red") + }); + + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", + new[] + { + new + { + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") + }, + new + { + Id = TString("gvclass_arr#2#Id"), + Color = TEnum("DebuggerTests.RGB", "Blue"), + Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green") + } + } + ); + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty", + new object[0]); + }); + } + + // TODO: Check previous frame too + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 170; + int col = 12; + string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; + int frame_idx = 0; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; + + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + t1 = TObject("DebuggerTests.SimpleGenericStruct"), + @this = TObject("DebuggerTests.ArrayTestsClass"), + point_arr = TArray("DebuggerTests.Point[]", 2), + point = TValueType("DebuggerTests.Point") + }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); + + await CompareObjectPropertiesFor(frame_locals, "t1", + new + { + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") + }); + + await CompareObjectPropertiesFor(frame_locals, "point_arr", + new[] + { + TPoint(5, -2, "point_arr#Id#0", "Red"), + TPoint(123, 0, "point_arr#Id#1", "Blue"), + } + ); + + await CompareObjectPropertiesFor(frame_locals, "point", + TPoint(45, 51, "point#Id", "Green")); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 244; + int col = 12; + string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; + int frame_idx = 0; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + //await SetBreakpoint (debugger_test_loc, 143, 3); + + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" + + "); }, 1);"; + + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + local_i = TNumber(5), + sc = TSimpleClass(10, 45, "sc#Id", "Blue") + }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 251; + int col = 12; + string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; + int frame_idx = 0; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; + + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "locals#0"); + + await CompareObjectPropertiesFor(frame_locals, "local_gs", + new + { + Id = TString("local_gs#Id"), + Color = TEnum("DebuggerTests.RGB", "Green"), + Value = TNumber(4) + }, + label: "local_gs#0"); + + await CompareObjectPropertiesFor(frame_locals, "sc_arg", + TSimpleClass(10, 45, "sc_arg#Id", "Blue"), + label: "sc_arg#0"); + + await CompareObjectPropertiesFor(frame_locals, "this", + TPoint(90, -4, "point#Id", "Green"), + label: "this#0"); + }); + } + + [Fact] + public async Task InvalidArrayId() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);", + wait_for_event_fn: async (pause_location) => + { + + int frame_idx = 1; + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + var c_obj = GetAndAssertObjectWithName(frame_locals, "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + Assert.NotNull(c_obj_id); + + // Invalid format + await GetProperties("dotnet:array:4123", expect_ok: false); + + // Invalid object id + await GetProperties("dotnet:array:{ \"arrayId\": 234980 }", expect_ok: false); + + // Trying to access object as an array + if (!DotnetObjectId.TryParse(c_obj_id, out var id) || id.Scheme != "object") + Assert.True(false, "Unexpected object id format. Maybe this test is out of sync with the object id format in library_mono.js?"); + + if (!int.TryParse(id.Value, out var idNum)) + Assert.True(false, "Expected a numeric value part of the object id: {c_obj_id}"); + await GetProperties($"dotnet:array:{{\"arrayId\":{idNum}}}", expect_ok: false); + }); + + [Fact] + public async Task InvalidValueTypeArrayIndex() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);", + locals_fn: async (locals) => + { + var this_obj = GetAndAssertObjectWithName(locals, "this"); + var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value()), "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + Assert.NotNull(c_obj_id); + + var c_props = await GetProperties(c_obj_id); + + var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField"); + var pf_arr_elems = await GetProperties(pf_arr["value"]["objectId"].Value()); + + if (!DotnetObjectId.TryParse(pf_arr_elems[0]["value"]?["objectId"]?.Value(), out var id)) + Assert.True(false, "Couldn't parse objectId for PointsFields' elements"); + + AssertEqual("valuetype", id.Scheme, "Expected a valuetype id"); + var id_args = id.ValueAsJson; + Assert.True(id_args["arrayId"] != null, "ObjectId format for array seems to have changed. Expected to find 'arrayId' in the value. Update this test"); + Assert.True(id_args != null, "Expected to get a json as the value part of {id}"); + + // Try one valid query, to confirm that the id format hasn't changed! + id_args["arrayIdx"] = 0; + await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: true); + + id_args["arrayIdx"] = 12399; + await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false); + + id_args["arrayIdx"] = -1; + await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false); + + id_args["arrayIdx"] = "qwe"; + await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false); + }); + + [Fact] + public async Task InvalidAccessors() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);", + locals_fn: async (locals) => + { + var this_obj = GetAndAssertObjectWithName(locals, "this"); + var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value()), "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + Assert.NotNull(c_obj_id); + + var c_props = await GetProperties(c_obj_id); + + var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField"); + + var invalid_accessors = new object[] { "NonExistant", "10000", "-2", 10000, -2, null, String.Empty }; + foreach (var invalid_accessor in invalid_accessors) + { + // var res = await InvokeGetter (JObject.FromObject (new { value = new { objectId = obj_id } }), invalid_accessor, expect_ok: true); + var res = await InvokeGetter(pf_arr, invalid_accessor, expect_ok: true); + AssertEqual("undefined", res.Value["result"]?["type"]?.ToString(), "Expected to get undefined result for non-existant accessor"); + } + }); + + } } diff --git a/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs b/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs index f51be601225e..d6169102bb12 100644 --- a/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs @@ -1,795 +1,981 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; namespace DebuggerTests { - public class CallFunctionOnTests : DebuggerTestBase { - - // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses - // Using this here as a non-trivial test case - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, 10, false)] - [InlineData ("big_array_js_test (0);", "/other.js", 5, 1, 0, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 10, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 0, true)] - public async Task CheckVSCodeTestFunction1 (string eval_fn, string bp_loc, int line, int col, int len, bool roundtrip) - { - string vscode_fn0 = "function(){const e={__proto__:this.__proto__},t=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(i>>>0)===n&&i>>>0!=4294967295)continue;const a=Object.getOwnPropertyDescriptor(this,n);a&&Object.defineProperty(e,n,a)}return e}"; - - await RunCallFunctionOn (eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array_len: len, roundtrip: roundtrip, - test_fn: async (result) => { - - var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal); - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - if (is_js) - await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors"); - else - AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count"); - - // Check for a __proto__ object - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - await CheckProps (obj_own.Value ["result"], new { - length = TNumber (len), - // __proto__ = TArray (type, 0) // Is this one really required? - }, $"obj_own", num_fields: is_js ? 2 : 1); - - }); - } - - void CheckJFunction (JToken actual, string className, string label) - { - AssertEqual ("function", actual ["type"]?.Value (), $"{label}-type"); - AssertEqual (className, actual ["className"]?.Value (), $"{label}-className"); - } - - // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses - // Using this here as a non-trivial test case - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, 10)] - [InlineData ("big_array_js_test (0);", "/other.js", 5, 1, 0)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 10)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 0)] - public async Task CheckVSCodeTestFunction2 (string eval_fn, string bp_loc, int line, int col, int len) - { - var fetch_start_idx = 2; - var num_elems_fetch = 3; - string vscode_fn1 = "function(e,t){const r={},n=-1===e?0:e,i=-1===t?this.length:e+t;for(let e=n;e { - - var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal); - - // isOwn = false, accessorPropertiesOnly = true - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - if (is_js) - await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors"); - else - AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count"); - - // Ignoring the __proto__ property - - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - var obj_own_val = obj_own.Value ["result"]; - var num_elems_recd = len == 0 ? 0 : num_elems_fetch; - AssertEqual (is_js ? num_elems_recd + 1 : num_elems_recd, obj_own_val.Count (), $"obj_own-count"); - - if (is_js) - CheckObject (obj_own_val, "__proto__", "Object"); - - for (int i = fetch_start_idx; i < fetch_start_idx + num_elems_recd; i ++) - CheckNumber (obj_own_val, i.ToString (), 1000 + i); - }); - } - - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)] - public async Task RunOnArrayReturnEmptyArray (string eval_fn, string bp_loc, int line, int col, bool roundtrip) - { - var ret_len = 0; - - await RunCallFunctionOn (eval_fn, - "function () { return []; }", - "big", bp_loc, line, col, - res_array_len: ret_len, - roundtrip: roundtrip, - test_fn: async (result) => { - var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal); - - // getProperties (isOwn = false, accessorPropertiesOnly = true) - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - if (is_js) - await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors"); - else - AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count"); - - // getProperties (isOwn = true, accessorPropertiesOnly = false) - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - await CheckProps (obj_own.Value ["result"], new { - length = TNumber (ret_len), - // __proto__ returned by js - }, $"obj_own", num_fields: is_js ? 2 : 1); - }); - } - - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)] - public async Task RunOnArrayReturnArray (string eval_fn, string bp_loc, int line, int col, bool roundtrip) - { - var ret_len = 5; - await RunCallFunctionOn (eval_fn, - "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }", - "big", bp_loc, line, col, - fn_args: JArray.FromObject (new [] { new { value = 2 } }), - res_array_len: ret_len, - roundtrip: roundtrip, - test_fn: async (result) => { - var is_js = bp_loc.EndsWith (".js"); - - // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - - if (is_js) - await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors"); - else - AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count"); - - // getProperties (own=true) - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count"); - - var obj_own_val = obj_own.Value ["result"]; - await CheckProps (obj_own_val, new { - length = TNumber (ret_len), - // __proto__ returned by JS - }, $"obj_own", num_fields: (is_js ? ret_len + 2 : ret_len + 1)); - - for (int i = 0; i < ret_len; i ++) - CheckNumber (obj_own_val, i.ToString (), i*2 + 1000); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task RunOnVTArray (bool roundtrip) - => await RunCallFunctionOn ( - "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", - "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }", - "ss_arr", - "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, - fn_args: JArray.FromObject (new [] { new { value = 2 } }), - res_array_len: 5, - roundtrip: roundtrip, - test_fn: async (result) => { - var ret_len = 5; - - // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - - AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count"); - - // getProperties (own=true) - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - var obj_own_val = obj_own.Value ["result"]; - await CheckProps (obj_own_val, new { - length = TNumber (ret_len), - // __proto__ returned by JS - }, "obj_own", num_fields: ret_len + 1); - - for (int i = 0; i < ret_len; i ++) { - var act_i = CheckValueType (obj_own_val, i.ToString (), "Math.SimpleStruct"); - - // Valuetypes can get sent as part of the container's getProperties, so ensure that we can access it - var act_i_props = await GetProperties (act_i ["value"]["objectId"]?.Value ()); - await CheckProps (act_i_props, new { - dt = TValueType ("System.DateTime", new DateTime (2020 + (i*2), 1, 2, 3, 4, 5).ToString ()), - gs = TValueType ("Math.GenericStruct") - }, "obj_own ss_arr[{i}]"); - - var gs_props = await GetObjectOnLocals (act_i_props, "gs"); - await CheckProps (gs_props, new { - List = TObject ("System.Collections.Generic.List", is_null: true), - StringField = TString ($"ss_arr # {i*2} # gs # StringField") - }, "obj_own ss_arr[{i}].gs"); - - } - }); - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task RunOnCFOValueTypeResult (bool roundtrip) - => await RunCallFunctionOn ( - eval_fn: "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", - fn_decl: "function () { return this; }", - local_name: "simple_struct", - bp_loc: "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, - roundtrip: roundtrip, - test_fn: async (result) => { - - // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - AssertEqual (0, obj_accessors.Value ["result"].Count (), "obj_accessors-count"); - - // getProperties (own=true) - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - var obj_own_val = obj_own.Value ["result"]; - var dt = new DateTime (2020, 1, 2, 3, 4, 5); - await CheckProps (obj_own_val, new { - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("Math.GenericStruct") - }, $"obj_own-props"); - - await CheckDateTime (obj_own_val, "dt", dt); - - var gs_props = await GetObjectOnLocals (obj_own_val, "gs"); - await CheckProps (gs_props, new { - List = TObject ("System.Collections.Generic.List", is_null: true), - StringField = TString ($"simple_struct # gs # StringField") - }, "simple_struct.gs-props"); - }); - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task RunOnJSObject (bool roundtrip) - => await RunCallFunctionOn ( - "object_js_test ();", - "function () { return this; }", - "obj", "/other.js", 14, 1, - fn_args: JArray.FromObject (new [] { new { value = 2 } }), - roundtrip: roundtrip, - test_fn: async (result) => { - - // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = true, - ownProperties = false - }), ctx.token); - - await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors"); - - // getProperties (own=true) - // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new { - objectId = result.Value ["result"]["objectId"].Value (), - accessorPropertiesOnly = false, - ownProperties = true - }), ctx.token); - - var obj_own_val = obj_own.Value ["result"]; - await CheckProps (obj_own_val, new { - a_obj = TObject ("Object"), - b_arr = TArray ("Array", 2) - }, "obj_own", num_fields: 3); - }); - - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)] - public async Task RunOnArrayReturnObjectArrayByValue (string eval_fn, string bp_loc, int line, int col, bool roundtrip) - { - var ret_len = 5; - await RunCallFunctionOn (eval_fn, - "function () { return Object.values (this).filter ((k, i) => i%2 == 0); }", - "big", bp_loc, line, col, returnByValue: true, roundtrip: roundtrip, - test_fn: async (result) => { - // Check cfo result - AssertEqual (JTokenType.Object, result.Value ["result"].Type, "cfo-result-jsontype"); - AssertEqual ("object", result.Value ["result"]["type"]?.Value (), "cfo-res-type"); - - AssertEqual (JTokenType.Array, result.Value ["result"] ["value"].Type, "cfo-res-value-jsontype"); - var actual = result.Value ["result"]?["value"].Values ().ToArray (); - AssertEqual (ret_len, actual.Length, "cfo-res-value-length"); - - for (int i = 0; i < ret_len; i ++) { - var exp_num = i*2 + 1000; - if (bp_loc.EndsWith (".js", StringComparison.Ordinal)) - AssertEqual (exp_num, actual [i].Value (), $"[{i}]"); - else { - AssertEqual ("number", actual [i]?["type"]?.Value (), $"[{i}]-type"); - AssertEqual (exp_num.ToString (), actual [i]?["description"]?.Value (), $"[{i}]-description"); - AssertEqual (exp_num, actual [i]?["value"]?.Value (), $"[{i}]-value"); - } - } - await Task.CompletedTask; - }); - } - - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)] - public async Task RunOnArrayReturnArrayByValue (string eval_fn, string bp_loc, int line, int col, bool roundtrip) - => await RunCallFunctionOn (eval_fn, - "function () { return Object.getOwnPropertyNames (this); }", - "big", bp_loc, line, col, returnByValue: true, - roundtrip: roundtrip, - test_fn: async (result) => { - // Check cfo result - AssertEqual ("object", result.Value ["result"]["type"]?.Value (), "cfo-res-type"); - - var exp = new JArray (); - for (int i = 0; i < 10; i ++) - exp.Add (i.ToString ()); - exp.Add ("length"); - - var actual = result.Value ["result"]?["value"]; - if (!JObject.DeepEquals (exp, actual)) { - Assert.True (false, $"Results don't match.\nExpected: {exp}\nActual: {actual}"); - } - await Task.CompletedTask; - }); - - [Theory] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)] - [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)] - public async Task RunOnArrayReturnPrimitive (string eval_fn, string bp_loc, int line, int col, bool return_by_val) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - await SetBreakpoint (bp_loc, line, col); - - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE); - - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); - - // Check the object at the bp - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value ()); - var obj = GetAndAssertObjectWithName (frame_locals, "big"); - var obj_id = obj ["value"]["objectId"].Value (); - - var cfo_args = JObject.FromObject (new { - functionDeclaration = "function () { return 5; }", - objectId = obj_id - }); - - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args ["returnByValue"] = return_by_val; - - // callFunctionOn - result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue (result.Value ["result"], TNumber (5), "cfo-res"); - }); - } - - public static TheoryData SilentErrorsTestData (bool? silent) - => new TheoryData { - { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, silent }, - { "big_array_js_test (10);", "/other.js", 5, 1, silent } - }; - - [Theory] - [MemberData (nameof (SilentErrorsTestData), null)] - [MemberData (nameof (SilentErrorsTestData), false)] - [MemberData (nameof (SilentErrorsTestData), true)] - public async Task CFOWithSilentReturnsErrors (string eval_fn, string bp_loc, int line, int col, bool? silent) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - await SetBreakpoint (bp_loc, line, col); - - // callFunctionOn - var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; - var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE); - - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value ()); - var obj = GetAndAssertObjectWithName (frame_locals, "big"); - var big_obj_id = obj ["value"]["objectId"].Value (); - var error_msg = "#This is an error message#"; - - // Check the object at the bp - var cfo_args = JObject.FromObject (new { - functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", - objectId = big_obj_id - }); - - if (silent.HasValue) - cfo_args ["silent"] = silent; - - // callFunctionOn, Silent does not change the result, except that the error - // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true - result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - Assert.False (result.IsOk, "result.IsOk"); - Assert.True (result.IsErr, "result.IsErr"); - - var hasErrorMessage = result.Error ["exceptionDetails"]?["exception"]?["description"]?.Value ()?.Contains (error_msg); - Assert.True ((hasErrorMessage ?? false), "Exception message not found"); - }); - } - - public static TheoryData, bool> GettersTestData (bool use_cfo) - => new TheoryData, bool> { - // Chrome sends this one - { - "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", - "PropertyGettersTest", 26, 3, - "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i JArray.FromObject (arg_strs).ToString (), - use_cfo - }, - { - "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", - "MoveNext", 34, 3, - "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i JArray.FromObject (arg_strs).ToString (), - use_cfo - }, - - // VSCode sends this one - { - "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", - "PropertyGettersTest", 26, 3, - "function(e){return this[e]}", - (args_str) => args_str?.Length > 0 ? args_str [0] : String.Empty, - use_cfo - }, - { - "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", - "MoveNext", 34, 3, - "function(e){return this[e]}", - (args_str) => args_str?.Length > 0 ? args_str [0] : String.Empty, - use_cfo - } - }; - - [Theory] - [MemberData (nameof (GettersTestData), parameters: false)] - [MemberData (nameof (GettersTestData), parameters: true)] - public async Task PropertyGettersOnObjectsTest (string eval_fn, string method_name, int line, int col, string cfo_fn, Func get_args_fn, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col, - method_name, - $"window.setTimeout(function() {{ {eval_fn} }}, 1);", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - var dt = new DateTime (10, 9, 8, 7, 6, 5); - - await CheckProps (frame_locals, new { - ptd = TObject ("DebuggerTests.ClassWithProperties"), - swp = TObject ("DebuggerTests.StructWithProperties"), - }, "locals#0"); - - var ptd = GetAndAssertObjectWithName (frame_locals, "ptd"); - - var ptd_props = await GetProperties (ptd? ["value"]?["objectId"]?.Value ()); - await CheckProps (ptd_props, new { - Int = TGetter ("Int"), - String = TGetter ("String"), - DT = TGetter ("DT"), - IntArray = TGetter ("IntArray"), - DTArray = TGetter ("DTArray") - }, "ptd", num_fields: 7); - - // Automatic properties don't have invokable getters, because we can get their - // value from the backing field directly - { - dt = new DateTime (4, 5, 6, 7, 8, 9); - var dt_auto_props = await GetObjectOnLocals (ptd_props, "DTAutoProperty"); - await CheckDateTime (ptd_props, "DTAutoProperty", dt); - } - - // Invoke getters, and check values - - var res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"Int"})); - Assert.True (res.IsOk, $"InvokeGetter failed with : {res}"); - await CheckValue (res.Value ["result"], JObject.FromObject (new { type = "number", value = 5 }), "ptd.Int"); - - res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"String"})); - Assert.True (res.IsOk, $"InvokeGetter failed with : {res}"); - await CheckValue (res.Value ["result"], JObject.FromObject (new { type = "string", value = "foobar" }), "ptd.String"); - - dt = new DateTime (3, 4, 5, 6, 7, 8); - res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"DT"})); - Assert.True (res.IsOk, $"InvokeGetter failed with : {res}"); - await CheckValue (res.Value ["result"], TValueType ("System.DateTime", dt.ToString ()), "ptd.DT"); - await CheckDateTimeValue (res.Value ["result"], dt); - - // Check arrays through getters - - res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"IntArray"})); - Assert.True (res.IsOk, $"InvokeGetter failed with : {res}"); - await CheckValue (res.Value ["result"], TArray ("int[]", 2), "ptd.IntArray"); - { - var arr_elems = await GetProperties (res.Value ["result"]?["objectId"]?.Value ()); - var exp_elems = new [] { - TNumber (10), - TNumber (20) - }; - - await CheckProps (arr_elems, exp_elems, "ptd.IntArray"); - } - - res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"DTArray"})); - Assert.True (res.IsOk, $"InvokeGetter failed with : {res}"); - await CheckValue (res.Value ["result"], TArray ("System.DateTime[]", 2), "ptd.DTArray"); - { - var dt0 = new DateTime (6, 7, 8, 9, 10, 11); - var dt1 = new DateTime (1, 2, 3, 4, 5, 6); - - var arr_elems = await GetProperties (res.Value ["result"]?["objectId"]?.Value ()); - var exp_elems = new [] { - TValueType ("System.DateTime", dt0.ToString ()), - TValueType ("System.DateTime", dt1.ToString ()), - }; - - await CheckProps (arr_elems, exp_elems, "ptd.DTArray"); - } - }); - - [Theory] - [InlineData ("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "MoveNext", 34, 3)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "PropertyGettersTest", 26, 3)] - public async Task PropertyGettersOnStructsTest (string eval_fn, string method_name, int line, int col) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col, - method_name, - $"window.setTimeout(function() {{ {eval_fn} }}, 1);", - wait_for_event_fn: async (pause_location) => { - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - await CheckProps (frame_locals, new { - ptd = TObject ("DebuggerTests.ClassWithProperties"), - swp = TObject ("DebuggerTests.StructWithProperties"), - }, "locals#0"); - - var swp = GetAndAssertObjectWithName (frame_locals, "swp"); - - var swp_props = await GetProperties (swp? ["value"]?["objectId"]?.Value ()); - await CheckProps (swp_props, new { - Int = TSymbol ("int { get; }"), - String = TSymbol ("string { get; }"), - DT = TSymbol ("System.DateTime { get; }"), - IntArray = TSymbol ("int[] { get; }"), - DTArray = TSymbol ("System.DateTime[] { get; }") - }, "swp"); - }); - - [Theory] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 26, 3, false)] - [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 26, 3, true)] - [InlineData ("invoke_getters_js_test ();", "/other.js", 26, 1, false)] - [InlineData ("invoke_getters_js_test ();", "/other.js", 26, 1, true)] - public async Task CheckAccessorsOnObjectsWithCFO (string eval_fn, string bp_loc, int line, int col, bool roundtrip) - { - await RunCallFunctionOn ( - eval_fn, "function() { return this; }", "ptd", - bp_loc, line, col, - roundtrip: roundtrip, - test_fn: async (result) => { - - var is_js = bp_loc.EndsWith (".js"); - - // Check with `accessorPropertiesOnly=true` - - var id = result.Value? ["result"]?["objectId"]?.Value (); - var get_prop_req = JObject.FromObject (new { - objectId = id, - accessorPropertiesOnly = true - }); - - var res = await GetPropertiesAndCheckAccessors (get_prop_req, is_js ? 6 : 5); // js returns extra `__proto__` member also - Assert.False (res.Value ["result"].Any (jt => jt ["name"]?.Value () == "StringField"), "StringField shouldn't be returned for `accessorPropertiesOnly`"); - - // Check with `accessorPropertiesOnly` unset, == false - get_prop_req = JObject.FromObject (new { - objectId = id, - }); - - res = await GetPropertiesAndCheckAccessors (get_prop_req, is_js ? 8 : 7); // js returns a `__proto__` member also - Assert.True (res.Value ["result"].Any (jt => jt ["name"]?.Value () == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`"); - }); - - async Task GetPropertiesAndCheckAccessors (JObject get_prop_req, int num_fields) - { - var res = await ctx.cli.SendCommand ("Runtime.getProperties", get_prop_req, ctx.token); - if (!res.IsOk) - Assert.True (false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {res}"); - - var accessors = new string[] { "Int", "String", "DT", "IntArray", "DTArray" }; - foreach (var name in accessors) { - var prop = GetAndAssertObjectWithName (res.Value ["result"], name); - Assert.True (prop ["value"] == null, $"{name} shouldn't have a `value`"); - - await CheckValue (prop, TGetter (name), $"{name}"); - } - - return res; - } - } - - async Task InvokeGetter (JToken obj, string fn, object arguments) - => await ctx.cli.SendCommand ( - "Runtime.callFunctionOn", - JObject.FromObject (new { - functionDeclaration = fn, - objectId = obj ["value"]?["objectId"]?.Value (), - arguments = new [] { new { value = arguments } } - }), ctx.token); - - /* - * 1. runs `Runtime.callFunctionOn` on the objectId, - * if @roundtrip == false, then - * -> calls @test_fn for that result (new objectId) - * else - * -> runs it again on the *result's* objectId. - * -> calls @test_fn on the *new* result objectId - * - * Returns: result of `Runtime.callFunctionOn` - */ - async Task RunCallFunctionOn (string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1, - Func test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - await SetBreakpoint (bp_loc, line, col); - - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE); - - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); - - // Check the object at the bp - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value ()); - var obj = GetAndAssertObjectWithName (frame_locals, local_name); - var obj_id = obj ["value"]["objectId"].Value (); - - var cfo_args = JObject.FromObject (new { - functionDeclaration = fn_decl, - objectId = obj_id - }); - - if (fn_args != null) - cfo_args ["arguments"] = fn_args; - - if (returnByValue) - cfo_args ["returnByValue"] = returnByValue; - - // callFunctionOn - result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckCFOResult (result); - - // If it wasn't `returnByValue`, then try to run a new function - // on that *returned* object - // This second function, just returns the object as-is, so the same - // test_fn is re-usable. - if (!returnByValue && roundtrip) { - cfo_args = JObject.FromObject (new { - functionDeclaration = "function () { return this; }", - objectId = result.Value ["result"]["objectId"]?.Value () - }); - - if (fn_args != null) - cfo_args ["arguments"] = fn_args; - - result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - - await CheckCFOResult (result); - } - - if (test_fn != null) - await test_fn (result); - - return; - - async Task CheckCFOResult (Result result) - { - if (returnByValue) - return; - - if (res_array_len < 0) - await CheckValue (result.Value ["result"], TObject ("Object"), $"cfo-res"); - else - await CheckValue (result.Value ["result"], TArray ("Array", res_array_len), $"cfo-res"); - } - }); - } - } + public class CallFunctionOnTests : DebuggerTestBase + { + + // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses + // Using this here as a non-trivial test case + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, 10, false)] + [InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0, true)] + public async Task CheckVSCodeTestFunction1(string eval_fn, string bp_loc, int line, int col, int len, bool roundtrip) + { + string vscode_fn0 = "function(){const e={__proto__:this.__proto__},t=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(i>>>0)===n&&i>>>0!=4294967295)continue;const a=Object.getOwnPropertyDescriptor(this,n);a&&Object.defineProperty(e,n,a)}return e}"; + + await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array_len: len, roundtrip: roundtrip, + test_fn: async (result) => + { + + var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + if (is_js) + await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); + else + AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); + + // Check for a __proto__ object + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + await CheckProps(obj_own.Value["result"], new + { + length = TNumber(len), + // __proto__ = TArray (type, 0) // Is this one really required? + }, $"obj_own", num_fields: is_js ? 2 : 1); + + }); + } + + void CheckJFunction(JToken actual, string className, string label) + { + AssertEqual("function", actual["type"]?.Value(), $"{label}-type"); + AssertEqual(className, actual["className"]?.Value(), $"{label}-className"); + } + + // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses + // Using this here as a non-trivial test case + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, 10)] + [InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0)] + public async Task CheckVSCodeTestFunction2(string eval_fn, string bp_loc, int line, int col, int len) + { + var fetch_start_idx = 2; + var num_elems_fetch = 3; + string vscode_fn1 = "function(e,t){const r={},n=-1===e?0:e,i=-1===t?this.length:e+t;for(let e=n;e + { + + var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); + + // isOwn = false, accessorPropertiesOnly = true + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + if (is_js) + await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); + else + AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); + + // Ignoring the __proto__ property + + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + var obj_own_val = obj_own.Value["result"]; + var num_elems_recd = len == 0 ? 0 : num_elems_fetch; + AssertEqual(is_js ? num_elems_recd + 1 : num_elems_recd, obj_own_val.Count(), $"obj_own-count"); + + if (is_js) + CheckObject(obj_own_val, "__proto__", "Object"); + + for (int i = fetch_start_idx; i < fetch_start_idx + num_elems_recd; i++) + CheckNumber(obj_own_val, i.ToString(), 1000 + i); + }); + } + + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] + public async Task RunOnArrayReturnEmptyArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip) + { + var ret_len = 0; + + await RunCallFunctionOn(eval_fn, + "function () { return []; }", + "big", bp_loc, line, col, + res_array_len: ret_len, + roundtrip: roundtrip, + test_fn: async (result) => + { + var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); + + // getProperties (isOwn = false, accessorPropertiesOnly = true) + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + if (is_js) + await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); + else + AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); + + // getProperties (isOwn = true, accessorPropertiesOnly = false) + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + await CheckProps(obj_own.Value["result"], new + { + length = TNumber(ret_len), + // __proto__ returned by js + }, $"obj_own", num_fields: is_js ? 2 : 1); + }); + } + + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] + public async Task RunOnArrayReturnArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip) + { + var ret_len = 5; + await RunCallFunctionOn(eval_fn, + "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }", + "big", bp_loc, line, col, + fn_args: JArray.FromObject(new[] { new { value = 2 } }), + res_array_len: ret_len, + roundtrip: roundtrip, + test_fn: async (result) => + { + var is_js = bp_loc.EndsWith(".js"); + + // getProperties (own=false) + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + + if (is_js) + await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); + else + AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); + + // getProperties (own=true) + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count"); + + var obj_own_val = obj_own.Value["result"]; + await CheckProps(obj_own_val, new + { + length = TNumber(ret_len), + // __proto__ returned by JS + }, $"obj_own", num_fields: (is_js ? ret_len + 2 : ret_len + 1)); + + for (int i = 0; i < ret_len; i++) + CheckNumber(obj_own_val, i.ToString(), i * 2 + 1000); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn( + "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", + "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }", + "ss_arr", + "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, + fn_args: JArray.FromObject(new[] { new { value = 2 } }), + res_array_len: 5, + roundtrip: roundtrip, + test_fn: async (result) => + { + var ret_len = 5; + + // getProperties (own=false) + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + + AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); + + // getProperties (own=true) + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + var obj_own_val = obj_own.Value["result"]; + await CheckProps(obj_own_val, new + { + length = TNumber(ret_len), + // __proto__ returned by JS + }, "obj_own", num_fields: ret_len + 1); + + for (int i = 0; i < ret_len; i++) + { + var act_i = CheckValueType(obj_own_val, i.ToString(), "Math.SimpleStruct"); + + // Valuetypes can get sent as part of the container's getProperties, so ensure that we can access it + var act_i_props = await GetProperties(act_i["value"]["objectId"]?.Value()); + await CheckProps(act_i_props, new + { + dt = TValueType("System.DateTime", new DateTime(2020 + (i * 2), 1, 2, 3, 4, 5).ToString()), + gs = TValueType("Math.GenericStruct") + }, "obj_own ss_arr[{i}]"); + + var gs_props = await GetObjectOnLocals(act_i_props, "gs"); + await CheckProps(gs_props, new + { + List = TObject("System.Collections.Generic.List", is_null: true), + StringField = TString($"ss_arr # {i * 2} # gs # StringField") + }, "obj_own ss_arr[{i}].gs"); + + } + }); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFunctionOn( + eval_fn: "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", + fn_decl: "function () { return this; }", + local_name: "simple_struct", + bp_loc: "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, + roundtrip: roundtrip, + test_fn: async (result) => + { + + // getProperties (own=false) + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); + + // getProperties (own=true) + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + var obj_own_val = obj_own.Value["result"]; + var dt = new DateTime(2020, 1, 2, 3, 4, 5); + await CheckProps(obj_own_val, new + { + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("Math.GenericStruct") + }, $"obj_own-props"); + + await CheckDateTime(obj_own_val, "dt", dt); + + var gs_props = await GetObjectOnLocals(obj_own_val, "gs"); + await CheckProps(gs_props, new + { + List = TObject("System.Collections.Generic.List", is_null: true), + StringField = TString($"simple_struct # gs # StringField") + }, "simple_struct.gs-props"); + }); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn( + "object_js_test ();", + "function () { return this; }", + "obj", "/other.js", 17, 1, + fn_args: JArray.FromObject(new[] { new { value = 2 } }), + roundtrip: roundtrip, + test_fn: async (result) => + { + + // getProperties (own=false) + var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), ctx.token); + + await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); + + // getProperties (own=true) + // isOwn = true, accessorPropertiesOnly = false + var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = false, + ownProperties = true + }), ctx.token); + + var obj_own_val = obj_own.Value["result"]; + await CheckProps(obj_own_val, new + { + a_obj = TObject("Object"), + b_arr = TArray("Array", 2) + }, "obj_own", num_fields: 3); + }); + + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] + public async Task RunOnArrayReturnObjectArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip) + { + var ret_len = 5; + await RunCallFunctionOn(eval_fn, + "function () { return Object.values (this).filter ((k, i) => i%2 == 0); }", + "big", bp_loc, line, col, returnByValue: true, roundtrip: roundtrip, + test_fn: async (result) => + { + // Check cfo result + AssertEqual(JTokenType.Object, result.Value["result"].Type, "cfo-result-jsontype"); + AssertEqual("object", result.Value["result"]["type"]?.Value(), "cfo-res-type"); + + AssertEqual(JTokenType.Array, result.Value["result"]["value"].Type, "cfo-res-value-jsontype"); + var actual = result.Value["result"]?["value"].Values().ToArray(); + AssertEqual(ret_len, actual.Length, "cfo-res-value-length"); + + for (int i = 0; i < ret_len; i++) + { + var exp_num = i * 2 + 1000; + if (bp_loc.EndsWith(".js", StringComparison.Ordinal)) + AssertEqual(exp_num, actual[i].Value(), $"[{i}]"); + else + { + AssertEqual("number", actual[i]?["type"]?.Value(), $"[{i}]-type"); + AssertEqual(exp_num.ToString(), actual[i]?["description"]?.Value(), $"[{i}]-description"); + AssertEqual(exp_num, actual[i]?["value"]?.Value(), $"[{i}]-value"); + } + } + await Task.CompletedTask; + }); + } + + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] + public async Task RunOnArrayReturnArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip) => await RunCallFunctionOn(eval_fn, + "function () { return Object.getOwnPropertyNames (this); }", + "big", bp_loc, line, col, returnByValue: true, + roundtrip: roundtrip, + test_fn: async (result) => + { + // Check cfo result + AssertEqual("object", result.Value["result"]["type"]?.Value(), "cfo-res-type"); + + var exp = new JArray(); + for (int i = 0; i < 10; i++) + exp.Add(i.ToString()); + exp.Add("length"); + + var actual = result.Value["result"]?["value"]; + if (!JObject.DeepEquals(exp, actual)) + { + Assert.True(false, $"Results don't match.\nExpected: {exp}\nActual: {actual}"); + } + await Task.CompletedTask; + }); + + [Theory] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)] + [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] + public async Task RunOnArrayReturnPrimitive(string eval_fn, string bp_loc, int line, int col, bool return_by_val) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var obj_id = obj["value"]["objectId"].Value(); + + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 5; }", + objectId = obj_id + }); + + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; + + // callFunctionOn + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + await CheckValue(result.Value["result"], TNumber(5), "cfo-res"); + + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 'test value'; }", + objectId = obj_id + }); + + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; + + // callFunctionOn + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res"); + + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return null; }", + objectId = obj_id + }); + + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; + + // callFunctionOn + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res"); + }); + } + + public static TheoryData SilentErrorsTestData(bool? silent) => new TheoryData + { { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, silent }, + { "big_array_js_test (10);", "/other.js", 8, 1, silent } + }; + + [Theory] + [MemberData(nameof(SilentErrorsTestData), null)] + [MemberData(nameof(SilentErrorsTestData), false)] + [MemberData(nameof(SilentErrorsTestData), true)] + public async Task CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int line, int col, bool? silent) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var big_obj_id = obj["value"]["objectId"].Value(); + var error_msg = "#This is an error message#"; + + // Check the object at the bp + var cfo_args = JObject.FromObject(new + { + functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", + objectId = big_obj_id + }); + + if (silent.HasValue) + cfo_args["silent"] = silent; + + // callFunctionOn, Silent does not change the result, except that the error + // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + Assert.False(result.IsOk, "result.IsOk"); + Assert.True(result.IsErr, "result.IsErr"); + + var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value()?.Contains(error_msg); + Assert.True((hasErrorMessage ?? false), "Exception message not found"); + }); + } + + public static TheoryData, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData, string, bool> + { + // Chrome sends this one + { + "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", + "PropertyGettersTest", + 30, + 12, + "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i JArray.FromObject(arg_strs).ToString(), + local_name, + use_cfo + }, + { + "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", + "MoveNext", + 38, + 12, + "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i JArray.FromObject(arg_strs).ToString(), + local_name, + use_cfo + }, + + // VSCode sends this one + { + "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", + "PropertyGettersTest", + 30, + 12, + "function(e){return this[e]}", + (args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty, + local_name, + use_cfo + }, + { + "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", + "MoveNext", + 38, + 12, + "function(e){return this[e]}", + (args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty, + local_name, + use_cfo + } + }; + + [Theory] + [MemberData(nameof(GettersTestData), "ptd", false)] + [MemberData(nameof(GettersTestData), "ptd", true)] + [MemberData(nameof(GettersTestData), "swp", false)] + [MemberData(nameof(GettersTestData), "swp", true)] + public async Task PropertyGettersTest(string eval_fn, string method_name, int line, int col, string cfo_fn, Func get_args_fn, string local_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col, + method_name, + $"window.setTimeout(function() {{ {eval_fn} }}, 1);", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + await CheckProps(frame_locals, new + { + ptd = TObject("DebuggerTests.ClassWithProperties"), + swp = TObject("DebuggerTests.StructWithProperties") + }, "locals#0"); + + var obj = GetAndAssertObjectWithName(frame_locals, local_name); + + var dt = new DateTime(4, 5, 6, 7, 8, 9); + var obj_props = await GetProperties(obj?["value"]?["objectId"]?.Value()); + await CheckProps(obj_props, new + { + V = TNumber(0xDEADBEEF), + Int = TGetter("Int"), + String = TGetter("String"), + DT = TGetter("DT"), + IntArray = TGetter("IntArray"), + DTArray = TGetter("DTArray"), + StringField = TString(null), + + // Auto properties show w/o getters, because they have + // a backing field + DTAutoProperty = TValueType("System.DateTime", dt.ToString()) + }, local_name); + + // Automatic properties don't have invokable getters, because we can get their + // value from the backing field directly + { + var dt_auto_props = await GetObjectOnLocals(obj_props, "DTAutoProperty"); + await CheckDateTime(obj_props, "DTAutoProperty", dt); + } + + // Invoke getters, and check values + + dt = new DateTime(3, 4, 5, 6, 7, 8); + var res = await InvokeGetter(obj, get_args_fn(new[] { "Int" }), cfo_fn); + await CheckValue(res.Value["result"], JObject.FromObject(new { type = "number", value = (0xDEADBEEF + (uint)dt.Month) }), $"{local_name}.Int"); + + res = await InvokeGetter(obj, get_args_fn(new[] { "String" }), cfo_fn); + await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = $"String property, V: 0xDEADBEEF" }), $"{local_name}.String"); + + res = await InvokeGetter(obj, get_args_fn(new[] { "DT" }), cfo_fn); + await CheckValue(res.Value["result"], TValueType("System.DateTime", dt.ToString()), $"{local_name}.DT"); + await CheckDateTimeValue(res.Value["result"], dt); + + // Check arrays through getters + + res = await InvokeGetter(obj, get_args_fn(new[] { "IntArray" }), cfo_fn); + await CheckValue(res.Value["result"], TArray("int[]", 2), $"{local_name}.IntArray"); + { + var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value()); + var exp_elems = new[] + { + TNumber(10), + TNumber(20) + }; + + await CheckProps(arr_elems, exp_elems, $"{local_name}.IntArray"); + } + + res = await InvokeGetter(obj, get_args_fn(new[] { "DTArray" }), cfo_fn); + await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), $"{local_name}.DTArray"); + { + var dt0 = new DateTime(6, 7, 8, 9, 10, 11); + var dt1 = new DateTime(1, 2, 3, 4, 5, 6); + + var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value()); + var exp_elems = new[] + { + TValueType("System.DateTime", dt0.ToString()), + TValueType("System.DateTime", dt1.ToString()), + }; + + await CheckProps(arr_elems, exp_elems, $"{local_name}.DTArray"); + + res = await InvokeGetter(arr_elems[0], "Date"); + await CheckDateTimeValue(res.Value["result"], dt0.Date); + } + }); + + [Theory] + [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, true)] + [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, false)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, true)] + [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, false)] + [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, false)] + [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, true)] + public async Task CheckAccessorsOnObjectsWithCFO(string eval_fn, string bp_loc, int line, int col, bool roundtrip) + { + await RunCallFunctionOn( + eval_fn, "function() { return this; }", "ptd", + bp_loc, line, col, + roundtrip: roundtrip, + test_fn: async (result) => + { + + var is_js = bp_loc.EndsWith(".js"); + + // Check with `accessorPropertiesOnly=true` + + var id = result.Value?["result"]?["objectId"]?.Value(); + var get_prop_req = JObject.FromObject(new + { + objectId = id, + accessorPropertiesOnly = true + }); + + var res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 6 : 5); // js returns extra `__proto__` member also + Assert.False(res.Value["result"].Any(jt => jt["name"]?.Value() == "StringField"), "StringField shouldn't be returned for `accessorPropertiesOnly`"); + + // Check with `accessorPropertiesOnly` unset, == false + get_prop_req = JObject.FromObject(new + { + objectId = id, + }); + + res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 8 : 7); // js returns a `__proto__` member also + Assert.True(res.Value["result"].Any(jt => jt["name"]?.Value() == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`"); + }); + + async Task GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_fields) + { + var res = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + if (!res.IsOk) + Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString()}, with Result: {res}"); + + var accessors = new string[] { "Int", "String", "DT", "IntArray", "DTArray" }; + foreach (var name in accessors) + { + var prop = GetAndAssertObjectWithName(res.Value["result"], name); + Assert.True(prop["value"] == null, $"{name} shouldn't have a `value`"); + + await CheckValue(prop, TGetter(name), $"{name}"); + } + + return res; + } + } + + public static TheoryData NegativeTestsData(bool use_cfo = false) => new TheoryData + { { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:MethodForNegativeTests', null);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 45, 12, use_cfo }, + { "negative_cfo_test ();", "/other.js", 62, 1, use_cfo } + }; + + [Theory] + [MemberData(nameof(NegativeTestsData), false)] + public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn( + eval_fn, "function() { return this; }", "ptd", + bp_loc, line, col, + test_fn: async (cfo_result) => + { + var ptd_id = cfo_result.Value?["result"]?["objectId"]?.Value(); + + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 0; }", + objectId = ptd_id + "_invalid" + }); + + var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + Assert.True(res.IsErr); + }); + + [Theory] + [MemberData(nameof(NegativeTestsData), false)] + public async Task RunOnInvalidThirdSegmentOfObjectId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); + + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 0; }", + objectId = ptd_id + "_invalid" + }); + + var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + Assert.True(res.IsErr); + }); + } + + [Theory] + [MemberData(nameof(NegativeTestsData), false)] + [MemberData(nameof(NegativeTestsData), true)] + public async Task InvalidPropertyGetters(string eval_fn, string bp_loc, int line, int col, bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpoint(bp_loc, line, col); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr })); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); + + var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 }; + foreach (var invalid_arg in invalid_args) + { + var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg); + AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}"); + } + }); + } + + [Theory] + [MemberData(nameof(NegativeTestsData), false)] + public async Task ReturnNullFromCFO(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn( + eval_fn, "function() { return this; }", "ptd", + bp_loc, line, col, + test_fn: async (result) => + { + var is_js = bp_loc.EndsWith(".js"); + var ptd = JObject.FromObject(new { value = new { objectId = result.Value?["result"]?["objectId"]?.Value() } }); + + var null_value_json = JObject.Parse("{ 'type': 'object', 'subtype': 'null', 'value': null }"); + foreach (var returnByValue in new bool?[] { null, false, true }) + { + var res = await InvokeGetter(ptd, "StringField", returnByValue: returnByValue); + if (is_js) + { + // In js case, it doesn't know the className, so the result looks slightly different + Assert.True( + JObject.DeepEquals(res.Value["result"], null_value_json), + $"[StringField#returnByValue = {returnByValue}] Json didn't match. Actual: {res.Value["result"]} vs {null_value_json}"); + } + else + { + await CheckValue(res.Value["result"], TString(null), "StringField"); + } + } + }); + + /* + * 1. runs `Runtime.callFunctionOn` on the objectId, + * if @roundtrip == false, then + * -> calls @test_fn for that result (new objectId) + * else + * -> runs it again on the *result's* objectId. + * -> calls @test_fn on the *new* result objectId + * + * Returns: result of `Runtime.callFunctionOn` + */ + async Task RunCallFunctionOn(string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1, + Func test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, local_name); + var obj_id = obj["value"]["objectId"].Value(); + + var cfo_args = JObject.FromObject(new + { + functionDeclaration = fn_decl, + objectId = obj_id + }); + + if (fn_args != null) + cfo_args["arguments"] = fn_args; + + if (returnByValue) + cfo_args["returnByValue"] = returnByValue; + + // callFunctionOn + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + await CheckCFOResult(result); + + // If it wasn't `returnByValue`, then try to run a new function + // on that *returned* object + // This second function, just returns the object as-is, so the same + // test_fn is re-usable. + if (!returnByValue && roundtrip) + { + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return this; }", + objectId = result.Value["result"]["objectId"]?.Value() + }); + + if (fn_args != null) + cfo_args["arguments"] = fn_args; + + result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + + await CheckCFOResult(result); + } + + if (test_fn != null) + await test_fn(result); + + return; + + async Task CheckCFOResult(Result result) + { + if (returnByValue) + return; + + if (res_array_len < 0) + await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res"); + else + await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res"); + } + }); + } + } } diff --git a/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs b/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs index 1b2ff400da3d..034b0613997e 100644 --- a/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs +++ b/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs @@ -1,61 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; +using System.Globalization; using System.Threading.Tasks; using Xunit; -using System.Globalization; namespace DebuggerTests { - public class DateTimeList : DebuggerTestBase { - - [Theory] - [InlineData ("en-US")] - [InlineData ("de-DE")] - [InlineData ("ka-GE")] - [InlineData ("hu-HU")] - + public class DateTimeList : DebuggerTestBase + { + + [Theory] + [InlineData("en-US")] + // Currently not passing tests. Issue #19743 // [InlineData ("ja-JP")] // [InlineData ("es-ES")] - public async Task CheckDateTimeLocale (string locale) { - var insp = new Inspector (); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; - - await SetBreakpointInMethod ("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," - + $"'{locale}'); }}, 1);", - debugger_test_loc, 20, 3, "LocaleTest", - locals_fn: async (locals) => { - DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; - CultureInfo.CurrentCulture = new CultureInfo (locale, false); - DateTime dt = new DateTime (2020, 1, 2, 3, 4, 5); - string dt_str = dt.ToString(); - - var fdtp = dtfi.FullDateTimePattern; - var ldp = dtfi.LongDatePattern; - var ltp = dtfi.LongTimePattern; - var sdp = dtfi.ShortDatePattern; - var stp = dtfi.ShortTimePattern; - - CheckString(locals, "fdtp", fdtp); - CheckString(locals, "ldp", ldp); - CheckString(locals, "ltp", ltp); - CheckString(locals, "sdp", sdp); - CheckString(locals, "stp", stp); - await CheckDateTime(locals, "dt", dt); - CheckString(locals, "dt_str", dt_str); - } - ); - - - }); - } - - } -} \ No newline at end of file + //[InlineData ("de-DE")] + //[InlineData ("ka-GE")] + //[InlineData ("hu-HU")] + public async Task CheckDateTimeLocale(string locale) + { + var insp = new Inspector(); + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; + + await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + + $"'{locale}'); }}, 1);", + debugger_test_loc, 25, 12, "LocaleTest", + locals_fn: async (locals) => + { + DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; + CultureInfo.CurrentCulture = new CultureInfo(locale, false); + DateTime dt = new DateTime(2020, 1, 2, 3, 4, 5); + string dt_str = dt.ToString(); + + var fdtp = dtfi.FullDateTimePattern; + var ldp = dtfi.LongDatePattern; + var ltp = dtfi.LongTimePattern; + var sdp = dtfi.ShortDatePattern; + var stp = dtfi.ShortTimePattern; + + CheckString(locals, "fdtp", fdtp); + CheckString(locals, "ldp", ldp); + CheckString(locals, "ltp", ltp); + CheckString(locals, "sdp", sdp); + CheckString(locals, "stp", stp); + await CheckDateTime(locals, "dt", dt); + CheckString(locals, "dt_str", dt_str); + } + ); + + }); + } + + } +} diff --git a/sdks/wasm/DebuggerTestSuite/DebuggerTestSuite.csproj b/sdks/wasm/DebuggerTestSuite/DebuggerTestSuite.csproj index c00fda67aaf2..8cd99732b994 100644 --- a/sdks/wasm/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/sdks/wasm/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/sdks/wasm/DebuggerTestSuite/DelegateTests.cs b/sdks/wasm/DebuggerTestSuite/DelegateTests.cs index 2b4c514cdee4..2fc0fb52ce99 100644 --- a/sdks/wasm/DebuggerTestSuite/DelegateTests.cs +++ b/sdks/wasm/DebuggerTestSuite/DelegateTests.cs @@ -1,284 +1,306 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; namespace DebuggerTests { - public class DelegateTests : DebuggerTestBase { - - [Theory] - [InlineData (0, 45, 2, "DelegatesTest", false)] - [InlineData (0, 45, 2, "DelegatesTest", true)] - [InlineData (2, 90, 2, "InnerMethod2", false)] - [InlineData (2, 90, 2, "InnerMethod2", true)] - public async Task InspectLocalsWithDelegatesAtBreakpointSite (int frame, int line, int col, string method_name, bool use_cfo) => - await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", line, col, method_name, - "window.setTimeout(function() { invoke_delegates_test (); }, 1);", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func", "bool |(Math)"), - fn_func_null = TObject ("System.Func", is_null: true), - fn_func_arr = TArray ("System.Func[]", 1), - fn_del = TDelegate ("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"), - fn_del_null = TObject ("Math.IsMathNull", is_null: true), - fn_del_arr = TArray ("Math.IsMathNull[]", 1), - - // Unused locals - fn_func_unused = TDelegate ("System.Func", "bool |(Math)"), - fn_func_null_unused = TObject ("System.Func", is_null: true), - fn_func_arr_unused = TArray ("System.Func[]", 1), - - fn_del_unused = TDelegate ("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"), - fn_del_null_unused = TObject ("Math.IsMathNull", is_null: true), - fn_del_arr_unused = TArray ("Math.IsMathNull[]", 1), - - res = TBool (false), - m_obj = TObject ("Math") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] { - TDelegate ( - "System.Func", - "bool |(Math)") - }, "locals#fn_func_arr"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] { - TDelegate ( - "Math.IsMathNull", - "bool IsMathNullDelegateTarget (Math)") - }, "locals#fn_del_arr"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr_unused", new [] { - TDelegate ( - "System.Func", - "bool |(Math)") - }, "locals#fn_func_arr_unused"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr_unused", new [] { - TDelegate ( - "Math.IsMathNull", - "bool IsMathNullDelegateTarget (Math)") - }, "locals#fn_del_arr_unused"); - } - ); - - [Theory] - [InlineData (0, 190, 2, "DelegatesSignatureTest", false)] - [InlineData (0, 190, 2, "DelegatesSignatureTest", true)] - [InlineData (2, 90, 2, "InnerMethod2", false)] - [InlineData (2, 90, 2, "InnerMethod2", true)] - public async Task InspectDelegateSignaturesWithFunc (int frame, int line, int col, string bp_method, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", - line, col, - bp_method, - "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesSignatureTest'); }, 1)", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func>, Math.GenericStruct>", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - - fn_func_del = TDelegate ("System.Func>, Math.GenericStruct>", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - - fn_func_null = TObject ("System.Func>, Math.GenericStruct>", is_null: true), - fn_func_only_ret= TDelegate ("System.Func", "bool |()"), - fn_func_arr = TArray ("System.Func>, Math.GenericStruct>[]", 1), - - fn_del = TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - - fn_del_l = TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - - fn_del_null = TObject ("Math.DelegateForSignatureTest", is_null: true), - fn_del_arr = TArray ("Math.DelegateForSignatureTest[]", 2), - m_obj = TObject ("Math"), - gs_gs = TValueType ("Math.GenericStruct>"), - fn_void_del = TDelegate ("Math.DelegateWithVoidReturn", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - - fn_void_del_arr = TArray ("Math.DelegateWithVoidReturn[]", 1), - fn_void_del_null= TObject ("Math.DelegateWithVoidReturn", is_null: true), - gs = TValueType ("Math.GenericStruct"), - rets = TArray ("Math.GenericStruct[]", 6) - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] { - TDelegate ( - "System.Func>, Math.GenericStruct>", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "locals#fn_func_arr"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] { - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)") - }, "locals#fn_del_arr"); - - await CompareObjectPropertiesFor (locals, "fn_void_del_arr", new [] { - TDelegate ( - "Math.DelegateWithVoidReturn", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)") - }, "locals#fn_void_del_arr"); - }); - - [Theory] - [InlineData (0, 211, 2, "ActionTSignatureTest", false)] - [InlineData (0, 211, 2, "ActionTSignatureTest", true)] - [InlineData (2, 90, 2, "InnerMethod2", false)] - [InlineData (2, 90, 2, "InnerMethod2", true)] - public async Task ActionTSignatureTest (int frame, int line, int col, string bp_method, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", line, col, - bp_method, - "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:ActionTSignatureTest'); }, 1)", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value()); - - await CheckProps (locals, new - { - fn_action = TDelegate ("System.Action>", - "void |(Math.GenericStruct)"), - fn_action_del = TDelegate ("System.Action>", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - fn_action_bare = TDelegate ("System.Action", - "void|()"), - - fn_action_null = TObject ("System.Action>", is_null: true), - - fn_action_arr = TArray ("System.Action>[]", 3), - - gs = TValueType ("Math.GenericStruct"), - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_action_arr", new [] { - TDelegate ( - "System.Action>", - "void |(Math.GenericStruct)"), - TDelegate ( - "System.Action>", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - TObject ("System.Action>", is_null: true) - }, "locals#fn_action_arr"); - }); - - [Theory] - [InlineData (0, 228, 2, "NestedDelegatesTest", false)] - [InlineData (0, 228, 2, "NestedDelegatesTest", true)] - [InlineData (2, 90, 2, "InnerMethod2", false)] - [InlineData (2, 90, 2, "InnerMethod2", true)] - public async Task NestedDelegatesTest (int frame, int line, int col, string bp_method, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", line, col, - bp_method, - "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:NestedDelegatesTest'); }, 1)", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func, bool>", - "bool |(Func)"), - fn_func_null = TObject ("System.Func, bool>", is_null: true), - fn_func_arr = TArray ("System.Func, bool>[]", 1), - fn_del_arr = TArray ("System.Func, bool>[]", 1), - - m_obj = TObject ("Math"), - fn_del_null = TObject ("System.Func, bool>", is_null: true), - fs = TDelegate ("System.Func", - "bool |(int)") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] { - TDelegate ( - "System.Func, bool>", - "bool |(System.Func)") - }, "locals#fn_func_arr"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] { - TDelegate ( - "System.Func, bool>", - "bool DelegateTargetForNestedFunc (Func)") - }, "locals#fn_del_arr"); - }); - - [Theory] - [InlineData (0, 247, 2, "MethodWithDelegateArgs", false)] - [InlineData (0, 247, 2, "MethodWithDelegateArgs", true)] - [InlineData (2, 90, 2, "InnerMethod2", false)] - [InlineData (2, 90, 2, "InnerMethod2", true)] - public async Task DelegatesAsMethodArgsTest (int frame, int line, int col, string bp_method, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", line, col, - bp_method, - "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesAsMethodArgsTest'); }, 1)", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value()); - - await CheckProps (locals, new { - @this = TObject ("Math"), - dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2), - fn_func = TDelegate ("System.Func", - "bool |(char[])"), - fn_action = TDelegate ("System.Action[]>", - "void |(Math.GenericStruct[])") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "dst_arr", new [] { - TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "locals#dst_arr"); - }); - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task MethodWithDelegatesAsyncTest (bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", 265, 2, - "MoveNext", //"DelegatesAsMethodArgsTestAsync" - "window.setTimeout (function () { invoke_static_method_async ('[debugger-test] Math:MethodWithDelegatesAsyncTest'); }, 1)", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - await CheckProps (locals, new { - @this = TObject ("Math"), - _dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2), - _fn_func = TDelegate ("System.Func", - "bool |(char[])"), - _fn_action = TDelegate ("System.Action[]>", - "void |(Math.GenericStruct[])") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "_dst_arr", new [] { - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "locals#dst_arr"); - }); - } + public class DelegateTests : DebuggerTestBase + { + + [Theory] + [InlineData(0, 53, 8, "DelegatesTest", false)] + [InlineData(0, 53, 8, "DelegatesTest", true)] + [InlineData(2, 99, 8, "InnerMethod2", false)] + [InlineData(2, 99, 8, "InnerMethod2", true)] + public async Task InspectLocalsWithDelegatesAtBreakpointSite(int frame, int line, int col, string method_name, bool use_cfo) => + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", line, col, method_name, + "window.setTimeout(function() { invoke_delegates_test (); }, 1);", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func", "bool |(Math)"), + fn_func_null = TObject("System.Func", is_null: true), + fn_func_arr = TArray("System.Func[]", 1), + fn_del = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"), + fn_del_null = TObject("Math.IsMathNull", is_null: true), + fn_del_arr = TArray("Math.IsMathNull[]", 1), + + // Unused locals + fn_func_unused = TDelegate("System.Func", "bool |(Math)"), + fn_func_null_unused = TObject("System.Func", is_null: true), + fn_func_arr_unused = TArray("System.Func[]", 1), + + fn_del_unused = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"), + fn_del_null_unused = TObject("Math.IsMathNull", is_null: true), + fn_del_arr_unused = TArray("Math.IsMathNull[]", 1), + + res = TBool(false), + m_obj = TObject("Math") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr", new[] + { + TDelegate( + "System.Func", + "bool |(Math)") + }, "locals#fn_func_arr"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr", new[] + { + TDelegate( + "Math.IsMathNull", + "bool IsMathNullDelegateTarget (Math)") + }, "locals#fn_del_arr"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr_unused", new[] + { + TDelegate( + "System.Func", + "bool |(Math)") + }, "locals#fn_func_arr_unused"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr_unused", new[] + { + TDelegate( + "Math.IsMathNull", + "bool IsMathNullDelegateTarget (Math)") + }, "locals#fn_del_arr_unused"); + } + ); + + [Theory] + [InlineData(0, 202, 8, "DelegatesSignatureTest", false)] + [InlineData(0, 202, 8, "DelegatesSignatureTest", true)] + [InlineData(2, 99, 8, "InnerMethod2", false)] + [InlineData(2, 99, 8, "InnerMethod2", true)] + public async Task InspectDelegateSignaturesWithFunc(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", + line, col, + bp_method, + "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesSignatureTest'); }, 1)", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func>, Math.GenericStruct>", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + + fn_func_del = TDelegate("System.Func>, Math.GenericStruct>", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + + fn_func_null = TObject("System.Func>, Math.GenericStruct>", is_null: true), + fn_func_only_ret = TDelegate("System.Func", "bool |()"), + fn_func_arr = TArray("System.Func>, Math.GenericStruct>[]", 1), + + fn_del = TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + + fn_del_l = TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + + fn_del_null = TObject("Math.DelegateForSignatureTest", is_null: true), + fn_del_arr = TArray("Math.DelegateForSignatureTest[]", 2), + m_obj = TObject("Math"), + gs_gs = TValueType("Math.GenericStruct>"), + fn_void_del = TDelegate("Math.DelegateWithVoidReturn", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + + fn_void_del_arr = TArray("Math.DelegateWithVoidReturn[]", 1), + fn_void_del_null = TObject("Math.DelegateWithVoidReturn", is_null: true), + gs = TValueType("Math.GenericStruct"), + rets = TArray("Math.GenericStruct[]", 6) + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr", new[] + { + TDelegate( + "System.Func>, Math.GenericStruct>", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "locals#fn_func_arr"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr", new[] + { + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)") + }, "locals#fn_del_arr"); + + await CompareObjectPropertiesFor(locals, "fn_void_del_arr", new[] + { + TDelegate( + "Math.DelegateWithVoidReturn", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)") + }, "locals#fn_void_del_arr"); + }); + + [Theory] + [InlineData(0, 224, 8, "ActionTSignatureTest", false)] + [InlineData(0, 224, 8, "ActionTSignatureTest", true)] + [InlineData(2, 99, 8, "InnerMethod2", false)] + [InlineData(2, 99, 8, "InnerMethod2", true)] + public async Task ActionTSignatureTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", line, col, + bp_method, + "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:ActionTSignatureTest'); }, 1)", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); + + await CheckProps(locals, new + { + fn_action = TDelegate("System.Action>", + "void |(Math.GenericStruct)"), + fn_action_del = TDelegate("System.Action>", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + fn_action_bare = TDelegate("System.Action", + "void|()"), + + fn_action_null = TObject("System.Action>", is_null: true), + + fn_action_arr = TArray("System.Action>[]", 3), + + gs = TValueType("Math.GenericStruct"), + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_action_arr", new[] + { + TDelegate( + "System.Action>", + "void |(Math.GenericStruct)"), + TDelegate( + "System.Action>", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + TObject("System.Action>", is_null : true) + }, "locals#fn_action_arr"); + }); + + [Theory] + [InlineData(0, 242, 8, "NestedDelegatesTest", false)] + [InlineData(0, 242, 8, "NestedDelegatesTest", true)] + [InlineData(2, 99, 8, "InnerMethod2", false)] + [InlineData(2, 99, 8, "InnerMethod2", true)] + public async Task NestedDelegatesTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", line, col, + bp_method, + "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:NestedDelegatesTest'); }, 1)", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func, bool>", + "bool |(Func)"), + fn_func_null = TObject("System.Func, bool>", is_null: true), + fn_func_arr = TArray("System.Func, bool>[]", 1), + fn_del_arr = TArray("System.Func, bool>[]", 1), + + m_obj = TObject("Math"), + fn_del_null = TObject("System.Func, bool>", is_null: true), + fs = TDelegate("System.Func", + "bool |(int)") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr", new[] + { + TDelegate( + "System.Func, bool>", + "bool |(System.Func)") + }, "locals#fn_func_arr"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr", new[] + { + TDelegate( + "System.Func, bool>", + "bool DelegateTargetForNestedFunc (Func)") + }, "locals#fn_del_arr"); + }); + + [Theory] + [InlineData(0, 262, 8, "MethodWithDelegateArgs", false)] + [InlineData(0, 262, 8, "MethodWithDelegateArgs", true)] + [InlineData(2, 99, 8, "InnerMethod2", false)] + [InlineData(2, 99, 8, "InnerMethod2", true)] + public async Task DelegatesAsMethodArgsTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", line, col, + bp_method, + "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesAsMethodArgsTest'); }, 1)", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value()); + + await CheckProps(locals, new + { + @this = TObject("Math"), + dst_arr = TArray("Math.DelegateForSignatureTest[]", 2), + fn_func = TDelegate("System.Func", + "bool |(char[])"), + fn_action = TDelegate("System.Action[]>", + "void |(Math.GenericStruct[])") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "dst_arr", new[] + { + TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "locals#dst_arr"); + }); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MethodWithDelegatesAsyncTest(bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", 281, 8, + "MoveNext", //"DelegatesAsMethodArgsTestAsync" + "window.setTimeout (function () { invoke_static_method_async ('[debugger-test] Math:MethodWithDelegatesAsyncTest'); }, 1)", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + await CheckProps(locals, new + { + @this = TObject("Math"), + _dst_arr = TArray("Math.DelegateForSignatureTest[]", 2), + _fn_func = TDelegate("System.Func", + "bool |(char[])"), + _fn_action = TDelegate("System.Action[]>", + "void |(Math.GenericStruct[])") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "_dst_arr", new[] + { + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "locals#dst_arr"); + }); + } } diff --git a/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs b/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs index 4fb0fd0f8669..5caae92eee1b 100644 --- a/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs +++ b/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs @@ -1,142 +1,167 @@ -using System; -using System.Threading.Tasks; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using System.Net.WebSockets; -using System.Threading; +using System; +using System.Collections.Generic; using System.IO; +using System.Net.WebSockets; using System.Text; -using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace WebAssembly.Net.Debugging { - internal class DevToolsClient: IDisposable { - ClientWebSocket socket; - List pending_ops = new List (); - TaskCompletionSource side_exit = new TaskCompletionSource (); - List pending_writes = new List (); - Task current_write; - readonly ILogger logger; - - public DevToolsClient (ILogger logger) { - this.logger = logger; - } - - ~DevToolsClient() { - Dispose(false); - } - - public void Dispose() { - Dispose(true); - } - - public async Task Close (CancellationToken cancellationToken) - { - if (socket.State == WebSocketState.Open) - await socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); - } - - protected virtual void Dispose (bool disposing) { - if (disposing) - socket.Dispose (); - } - - Task Pump (Task task, CancellationToken token) - { - if (task != current_write) - return null; - current_write = null; - - pending_writes.RemoveAt (0); - - if (pending_writes.Count > 0) { - current_write = socket.SendAsync (new ArraySegment (pending_writes [0]), WebSocketMessageType.Text, true, token); - return current_write; - } - return null; - } - - async Task ReadOne (CancellationToken token) - { - byte [] buff = new byte [4000]; - var mem = new MemoryStream (); - while (true) { - var result = await this.socket.ReceiveAsync (new ArraySegment (buff), token); - if (result.MessageType == WebSocketMessageType.Close) { - return null; - } - - if (result.EndOfMessage) { - mem.Write (buff, 0, result.Count); - return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length); - } else { - mem.Write (buff, 0, result.Count); - } - } - } - - protected void Send (byte [] bytes, CancellationToken token) - { - pending_writes.Add (bytes); - if (pending_writes.Count == 1) { - if (current_write != null) - throw new Exception ("Internal state is bad. current_write must be null if there are no pending writes"); - - current_write = socket.SendAsync (new ArraySegment (bytes), WebSocketMessageType.Text, true, token); - pending_ops.Add (current_write); - } - } - - async Task MarkCompleteAfterward (Func send, CancellationToken token) - { - try { - await send(token); - side_exit.SetResult (true); - } catch (Exception e) { - side_exit.SetException (e); - } - } - - protected async Task ConnectWithMainLoops( - Uri uri, - Func receive, - Func send, - CancellationToken token) { - - logger.LogDebug ("connecting to {0}", uri); - this.socket = new ClientWebSocket (); - this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; - - await this.socket.ConnectAsync (uri, token); - pending_ops.Add (ReadOne (token)); - pending_ops.Add (side_exit.Task); - pending_ops.Add (MarkCompleteAfterward (send, token)); - - while (!token.IsCancellationRequested) { - var task = await Task.WhenAny (pending_ops); - if (task == pending_ops [0]) { //pending_ops[0] is for message reading - var msg = ((Task)task).Result; - pending_ops [0] = ReadOne (token); - Task tsk = receive (msg, token); - if (tsk != null) - pending_ops.Add (tsk); - } else if (task == pending_ops [1]) { - var res = ((Task)task).Result; - //it might not throw if exiting successfull - return res; - } else { //must be a background task - pending_ops.Remove (task); - var tsk = Pump (task, token); - if (tsk != null) - pending_ops.Add (tsk); - } - } - - return false; - } - - protected virtual void Log (string priority, string msg) - { - // - } - } +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class DevToolsClient : IDisposable + { + ClientWebSocket socket; + List pending_ops = new List(); + TaskCompletionSource side_exit = new TaskCompletionSource(); + List pending_writes = new List(); + Task current_write; + readonly ILogger logger; + + public DevToolsClient(ILogger logger) + { + this.logger = logger; + } + + ~DevToolsClient() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + } + + public async Task Close(CancellationToken cancellationToken) + { + if (socket.State == WebSocketState.Open) + await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + socket.Dispose(); + } + + Task Pump(Task task, CancellationToken token) + { + if (task != current_write) + return null; + current_write = null; + + pending_writes.RemoveAt(0); + + if (pending_writes.Count > 0) + { + current_write = socket.SendAsync(new ArraySegment(pending_writes[0]), WebSocketMessageType.Text, true, token); + return current_write; + } + return null; + } + + async Task ReadOne(CancellationToken token) + { + byte[] buff = new byte[4000]; + var mem = new MemoryStream(); + while (true) + { + var result = await this.socket.ReceiveAsync(new ArraySegment(buff), token); + if (result.MessageType == WebSocketMessageType.Close) + { + return null; + } + + if (result.EndOfMessage) + { + mem.Write(buff, 0, result.Count); + return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); + } + else + { + mem.Write(buff, 0, result.Count); + } + } + } + + protected void Send(byte[] bytes, CancellationToken token) + { + pending_writes.Add(bytes); + if (pending_writes.Count == 1) + { + if (current_write != null) + throw new Exception("Internal state is bad. current_write must be null if there are no pending writes"); + + current_write = socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); + pending_ops.Add(current_write); + } + } + + async Task MarkCompleteAfterward(Func send, CancellationToken token) + { + try + { + await send(token); + side_exit.SetResult(true); + } + catch (Exception e) + { + side_exit.SetException(e); + } + } + + protected async Task ConnectWithMainLoops( + Uri uri, + Func receive, + Func send, + CancellationToken token) + { + + logger.LogDebug("connecting to {0}", uri); + this.socket = new ClientWebSocket(); + this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; + + await this.socket.ConnectAsync(uri, token); + pending_ops.Add(ReadOne(token)); + pending_ops.Add(side_exit.Task); + pending_ops.Add(MarkCompleteAfterward(send, token)); + + while (!token.IsCancellationRequested) + { + var task = await Task.WhenAny(pending_ops); + if (task == pending_ops[0]) + { //pending_ops[0] is for message reading + var msg = ((Task)task).Result; + pending_ops[0] = ReadOne(token); + Task tsk = receive(msg, token); + if (tsk != null) + pending_ops.Add(tsk); + } + else if (task == pending_ops[1]) + { + var res = ((Task)task).Result; + //it might not throw if exiting successfull + return res; + } + else + { //must be a background task + pending_ops.Remove(task); + var tsk = Pump(task, token); + if (tsk != null) + pending_ops.Add(tsk); + } + } + + return false; + } + + protected virtual void Log(string priority, string msg) + { + // + } + } } diff --git a/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 315ee5f522cb..14da2b0d2be6 100644 --- a/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1,204 +1,466 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; namespace DebuggerTests { + // TODO: static async, static method args + public class EvaluateOnCallFrameTests : DebuggerTestBase + { + public static IEnumerable InstanceMethodsTestData(string type_name) + { + yield return new object[] { type_name, "InstanceMethod", "InstanceMethod", false }; + yield return new object[] { type_name, "GenericInstanceMethod", "GenericInstanceMethod", false }; + yield return new object[] { type_name, "InstanceMethodAsync", "MoveNext", true }; + yield return new object[] { type_name, "GenericInstanceMethodAsync", "MoveNext", true }; + + // TODO: { "DebuggerTests.EvaluateTestsGeneric`1", "Instance", 9, "EvaluateTestsGenericStructInstanceMethod", prefix } + } + + public static IEnumerable InstanceMethodForTypeMembersTestData(string type_name) + { + foreach (var data in InstanceMethodsTestData(type_name)) + { + yield return new object[] { "", 0 }.Concat(data).ToArray(); + yield return new object[] { "this.", 0 }.Concat(data).ToArray(); + yield return new object[] { "NewInstance.", 3 }.Concat(data).ToArray(); + yield return new object[] { "this.NewInstance.", 3 }.Concat(data).ToArray(); + } + } + + [Theory] + [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")] + [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")] + public async Task EvaluateTypeInstanceMembers(string prefix, int bias, string type, string method, string bp_function_name, bool is_async) + => await CheckInspectLocalsAtBreakpointSite( + type, method, /*line_offset*/1, bp_function_name, + $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); + var DTProp = dateTime.AddMinutes(10); + + foreach (var pad in new[] { String.Empty, " " }) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + ($"{padded_prefix}a", TNumber(4)), + + // fields + ($"{padded_prefix}dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())), + ($"{padded_prefix}dateTime", TDateTime(dateTime)), + ($"{padded_prefix}dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), + + // properties + ($"{padded_prefix}DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), + ($"{padded_prefix}DTProp", TDateTime(DTProp)), + ($"{padded_prefix}DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())), + + ($"{padded_prefix}IntProp", TNumber(9)), + ($"{padded_prefix}NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true)) + ); + } + }); + + [Theory] + [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")] + [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")] + public async Task EvaluateInstanceMethodArguments(string type, string method, string bp_function_name, bool is_async) + => await CheckInspectLocalsAtBreakpointSite( + type, method, /*line_offset*/1, bp_function_name, + $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var DTProp = new DateTime(2010, 9, 8, 7, 6, 5).AddMinutes(10); + await EvaluateOnCallFrameAndCheck(id, + ("g", TNumber(400)), + ("h", TNumber(123)), + ("valString", TString("just a test")), + ("me", TObject(type)), + + // property on method arg + ("me.DTProp", TDateTime(DTProp)), + ("me.DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), + ("me.DTProp.Second + (me.IntProp - 5)", TNumber(DTProp.Second + 4))); + }); + + [Theory] + [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")] + [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")] + public async Task EvaluateMethodLocals(string type, string method, string bp_function_name, bool is_async) + => await CheckInspectLocalsAtBreakpointSite( + type, method, /*line_offset*/5, bp_function_name, + $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var dt = new DateTime(2025, 3, 5, 7, 9, 11); + await EvaluateOnCallFrameAndCheck(id, + ("d", TNumber(401)), + (" d", TNumber(401)), + ("e", TNumber(402)), + ("f", TNumber(403)), + + // property on a local + ("local_dt", TDateTime(dt)), + (" local_dt", TDateTime(dt)), + ("local_dt.Date", TDateTime(dt.Date)), + (" local_dt.Date", TDateTime(dt.Date))); + }); + + [Fact] + public async Task EvaluateStaticLocalsWithDeepMemberAccess() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateTestsClass", "EvaluateLocals", 9, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var dt = new DateTime(2020, 1, 2, 3, 4, 5); + await EvaluateOnCallFrameAndCheck(id, + ("f_s.c", TNumber(4)), + ("f_s", TValueType("DebuggerTests.EvaluateTestsStructWithProperties")), + + ("f_s.dateTime", TDateTime(dt)), + ("f_s.dateTime.Date", TDateTime(dt.Date))); + }); + + [Fact] + public async Task EvaluateLocalsAsync() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.Point", "AsyncInstanceMethod", 1, "MoveNext", + "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + // sc_arg + { + var (sc_arg, _) = await EvaluateOnCallFrame(id, "sc_arg"); + await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), nameof(sc_arg)); + + // Check that we did get the correct object + var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); + await CheckProps(sc_arg_props, new + { + X = TNumber(10), + Y = TNumber(45), + Id = TString("sc#Id"), + Color = TEnum("DebuggerTests.RGB", "Blue"), + PointWithCustomGetter = TGetter("PointWithCustomGetter") + }, "sc_arg_props#1"); + + await EvaluateOnCallFrameAndCheck(id, + ("(sc_arg.PointWithCustomGetter.X)", TNumber(100)), + ("sc_arg.Id + \"_foo\"", TString($"sc#Id_foo")), + ("sc_arg.Id + (sc_arg.X==10 ? \"_is_ten\" : \"_not_ten\")", TString($"sc#Id_is_ten"))); + } + + // local_gs + { + var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs"); + await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), nameof(local_gs)); + + var local_gs_props = await GetProperties(local_gs["objectId"]?.Value()); + await CheckProps(local_gs_props, new + { + Id = TObject("string", is_null: true), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TNumber(0) + }, "local_gs_props#1"); + await EvaluateOnCallFrameAndCheck(id, ("(local_gs.Id)", TString(null))); + } + }); + + [Theory] + [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")] + [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")] + public async Task EvaluateExpressionsWithDeepMemberAccesses(string prefix, int bias, string type, string method, string bp_function_name, bool _) + => await CheckInspectLocalsAtBreakpointSite( + type, method, /*line_offset*/4, bp_function_name, + $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); + var DTProp = dateTime.AddMinutes(10); + + await EvaluateOnCallFrameAndCheck(id, + ($"{prefix}a + 5", TNumber(9)), + ($"10 + {prefix}IntProp", TNumber(19)), + ($" {prefix}IntProp + {prefix}DTProp.Second", TNumber(9 + DTProp.Second)), + ($" {prefix}IntProp + ({prefix}DTProp.Second+{prefix}dateTime.Year)", TNumber(9 + DTProp.Second + dateTime.Year)), + ($" {prefix}DTProp.Second > 0 ? \"_foo_\": \"_zero_\"", TString("_foo_")), + + // local_dt is not live yet + ($"local_dt.Date.Year * 10", TNumber(10))); + }); + + [Fact] + public async Task EvaluateSimpleExpressions() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + // "((this))", TObject("foo")); //FIXME: + // "((dt))", TObject("foo")); //FIXME: + + ("this", TObject("DebuggerTests.EvaluateTestsClass.TestEvaluate")), + (" this", TObject("DebuggerTests.EvaluateTestsClass.TestEvaluate")), + + ("5", TNumber(5)), + (" 5", TNumber(5)), + ("d + e", TNumber(203)), + ("e + 10", TNumber(112)), + + // repeated expressions + ("this.a + this.a", TNumber(2)), + ("a + \"_\" + a", TString("9000_9000")), + ("a+(a )", TString("90009000")), + + // possible duplicate arg name + ("this.a + this_a", TNumber(46)), + + ("this.a + this.b", TNumber(3)), + ("\"test\" + \"test\"", TString("testtest")), + ("5 + 5", TNumber(10))); + }); + + public static TheoryData ShadowMethodArgsTestData => new TheoryData + { + { "DebuggerTests.EvaluateTestsClassWithProperties", "EvaluateShadow", "EvaluateShadow" }, + { "DebuggerTests.EvaluateTestsClassWithProperties", "EvaluateShadowAsync", "MoveNext" }, + { "DebuggerTests.EvaluateTestsStructWithProperties", "EvaluateShadow", "EvaluateShadow" }, + { "DebuggerTests.EvaluateTestsStructWithProperties", "EvaluateShadowAsync", "MoveNext" }, + }; + + [Theory] + [MemberData(nameof(ShadowMethodArgsTestData))] + public async Task LocalsAndArgsShadowingThisMembers(string type_name, string method, string bp_function_name) => await CheckInspectLocalsAtBreakpointSite( + type_name, method, 2, bp_function_name, + "window.setTimeout(function() { invoke_static_method ('[debugger-test] " + type_name + ":run'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + ("a", TString("hello")), + ("this.a", TNumber(4))); + + await CheckExpressions("this.", new DateTime(2010, 9, 8, 7, 6, 5 + 0)); + await CheckExpressions(String.Empty, new DateTime(2020, 3, 4, 5, 6, 7)); + + async Task CheckExpressions(string prefix, DateTime dateTime) + { + await EvaluateOnCallFrameAndCheck(id, + (prefix + "dateTime", TDateTime(dateTime)), + (prefix + "dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), + (prefix + "dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString()))); + } + }); + + [Theory] + [InlineData("DebuggerTests.EvaluateTestsStructWithProperties", true)] + [InlineData("DebuggerTests.EvaluateTestsClassWithProperties", false)] + public async Task EvaluateOnPreviousFrames(string type_name, bool is_valuetype) => await CheckInspectLocalsAtBreakpointSite( + type_name, "EvaluateShadow", 1, "EvaluateShadow", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] {type_name}:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var dt_local = new DateTime(2020, 3, 4, 5, 6, 7); + var dt_this = new DateTime(2010, 9, 8, 7, 6, 5); + + // At EvaluateShadow + { + var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id0, + ("dateTime", TDateTime(dt_local)), + ("this.dateTime", TDateTime(dt_this)) + ); + + await EvaluateOnCallFrameFail(id0, ("obj.IntProp", "ReferenceError")); + } + + { + var id1 = pause_location["callFrames"][1]["callFrameId"].Value(); + await EvaluateOnCallFrameFail(id1, + ("dateTime", "ReferenceError"), + ("this.dateTime", "ReferenceError")); + + // obj available only on the -1 frame + await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7))); + } + + await SetBreakpointInMethod("debugger-test.dll", type_name, "SomeMethod", 1); + pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "SomeMethod"); + + // At SomeMethod + + // TODO: change types also.. so, that `this` is different! + + // Check frame0 + { + var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); + + // 'me' and 'dateTime' are reversed in this method + await EvaluateOnCallFrameAndCheck(id0, + ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)), + ("this.dateTime", TDateTime(dt_this)), + ("me", TDateTime(dt_local)), + + // local variable shadows field, but isn't "live" yet + ("DTProp", TString(null)), + + // access field via `this.` + ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); + + await EvaluateOnCallFrameFail(id0, ("obj", "ReferenceError")); + } + + // check frame1 + { + var id1 = pause_location["callFrames"][1]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id1, + // 'me' and 'dateTime' are reversed in this method + ("dateTime", TDateTime(dt_local)), + ("this.dateTime", TDateTime(dt_this)), + ("me", is_valuetype ? TValueType(type_name) : TObject(type_name)), + + // not shadowed here + ("DTProp", TDateTime(dt_this.AddMinutes(10))), + + // access field via `this.` + ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); + + await EvaluateOnCallFrameFail(id1, ("obj", "ReferenceError")); + } + + // check frame2 + { + var id2 = pause_location["callFrames"][2]["callFrameId"].Value(); + + // Only obj should be available + await EvaluateOnCallFrameFail(id2, + ("dateTime", "ReferenceError"), + ("this.dateTime", "ReferenceError"), + ("me", "ReferenceError")); + + await EvaluateOnCallFrameAndCheck(id2, ("obj", is_valuetype ? TValueType(type_name) : TObject(type_name))); + } + }); + + [Fact] + public async Task JSEvaluate() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + var bp_loc = "/other.js"; + var line = 76; + var col = 1; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpoint(bp_loc, line, col); + + var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)"; + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameFail(id, + ("me.foo", null), + ("obj.foo.bar", null)); + + await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true); + }); + } + + [Fact] + public async Task NegativeTestsInInstanceMethod() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + // Use '.' on a primitive member + await EvaluateOnCallFrameFail(id, + //BUG: TODO: + //("a)", "CompilationError"), + + ("this.a.", "ReferenceError"), + ("a.", "ReferenceError"), + + ("this..a", "CompilationError"), + (".a.", "ReferenceError"), + + ("me.foo", "ReferenceError"), + + ("this.a + non_existant", "ReferenceError"), + + ("this.NullIfAIsNotZero.foo", "ReferenceError"), + ("NullIfAIsNotZero.foo", "ReferenceError")); + }); + + [Fact] + public async Task NegativeTestsInStaticMethod() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateTestsClass", "EvaluateLocals", 9, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameFail(id, + ("me.foo", "ReferenceError"), + ("this", "ReferenceError"), + ("this.NullIfAIsNotZero.foo", "ReferenceError")); + }); + + async Task EvaluateOnCallFrameAndCheck(string call_frame_id, params (string expression, JObject expected)[] args) + { + foreach (var arg in args) + { + var (eval_val, _) = await EvaluateOnCallFrame(call_frame_id, arg.expression); + try + { + await CheckValue(eval_val, arg.expected, arg.expression); + } + catch + { + Console.WriteLine($"CheckValue failed for {arg.expression}. Expected: {arg.expected}, vs {eval_val}"); + throw; + } + } + } - public class EvaluateOnCallFrameTests : DebuggerTestBase { - - [Fact] - public async Task EvaluateThisProperties () - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16, - "run", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "c"); - CheckContentValue (evaluate, "3"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "dt"); - await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1)); - }); - - [Theory] - [InlineData (58, 3, "EvaluateTestsStructInstanceMethod")] - [InlineData (74, 3, "GenericInstanceMethodOnStruct")] - [InlineData (97, 3, "EvaluateTestsGenericStructInstanceMethod")] - public async Task EvaluateThisPropertiesOnStruct (int line, int col, string method_name) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", line, col, - method_name, - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "c"); - CheckContentValue (evaluate, "3"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "dateTime"); - await CheckDateTimeValue (evaluate, new DateTime (2020, 1, 2, 3, 4, 5)); - }); - - [Fact] - public async Task EvaluateParameters () - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16, - "run", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "g"); - CheckContentValue (evaluate, "100"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "h"); - CheckContentValue (evaluate, "200"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "valString"); - CheckContentValue (evaluate, "test"); - }); - - [Fact] - public async Task EvaluateLocals () - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16, - "run", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "d"); - CheckContentValue (evaluate, "101"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "e"); - CheckContentValue (evaluate, "102"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "f"); - CheckContentValue (evaluate, "103"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "local_dt"); - await CheckDateTimeValue (evaluate, new DateTime (2010, 9, 8, 7, 6, 5)); - }); - - [Fact] - public async Task EvaluateLocalsAsync () - { - var bp_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - int line = 227; int col = 3; - var function_name = "MoveNext"; - await CheckInspectLocalsAtBreakpointSite ( - bp_loc, line, col, - function_name, - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - - // sc_arg - { - var sc_arg = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "sc_arg"); - await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#1"); - - var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value ()); - await CheckProps (sc_arg_props, new { - X = TNumber (10), - Y = TNumber (45), - Id = TString ("sc#Id"), - Color = TEnum ("DebuggerTests.RGB", "Blue"), - PointWithCustomGetter = TGetter ("PointWithCustomGetter") - }, "sc_arg_props#1"); - } - - // local_gs - { - var local_gs = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "local_gs"); - await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct"), "local_gs#1"); - - var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value ()); - await CheckProps (local_gs_props, new { - Id = TObject ("string", is_null: true), - Color = TEnum ("DebuggerTests.RGB", "Red"), - Value = TNumber (0) - }, "local_gs_props#1"); - } - - // step, check local_gs - pause_location = await StepAndCheck (StepKind.Over, bp_loc, line + 1, col, function_name); - { - var local_gs = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "local_gs"); - await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct"), "local_gs#2"); - - var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value ()); - await CheckProps (local_gs_props, new { - Id = TString ("local_gs#Id"), - Color = TEnum ("DebuggerTests.RGB", "Green"), - Value = TNumber (4) - }, "local_gs_props#2"); - } - - // step check sc_arg.Id - pause_location = await StepAndCheck (StepKind.Over, bp_loc, line + 2, col, function_name); - { - var sc_arg = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "sc_arg"); - await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#2"); - - var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value ()); - await CheckProps (sc_arg_props, new { - X = TNumber (10), - Y = TNumber (45), - Id = TString ("sc_arg#Id"), // <------- This changed - Color = TEnum ("DebuggerTests.RGB", "Blue"), - PointWithCustomGetter = TGetter ("PointWithCustomGetter") - }, "sc_arg_props#2"); - } - }); - } - - [Fact] - public async Task EvaluateExpressions () - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16, - "run", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "d + e"); - CheckContentValue (evaluate, "203"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "e + 10"); - CheckContentValue (evaluate, "112"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "a + a"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.a + this.b"); - CheckContentValue (evaluate, "3"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "\"test\" + \"test\""); - CheckContentValue (evaluate, "testtest"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "5 + 5"); - CheckContentValue (evaluate, "10"); - }); - - [Fact] - public async Task EvaluateThisExpressions () - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16, - "run", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })", - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.c"); - CheckContentValue (evaluate, "3"); - - // FIXME: not supported yet - // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.dt"); - // await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1)); - }); - } + async Task EvaluateOnCallFrameFail(string call_frame_id, params (string expression, string class_name)[] args) + { + foreach (var arg in args) + { + var (_, res) = await EvaluateOnCallFrame(call_frame_id, arg.expression, expect_ok: false); + if (arg.class_name != null) + AssertEqual(arg.class_name, res.Error["result"]?["className"]?.Value(), $"Error className did not match for expression '{arg.expression}'"); + } + } + } } diff --git a/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs b/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs new file mode 100644 index 000000000000..415956a6c2e3 --- /dev/null +++ b/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs @@ -0,0 +1,264 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace DebuggerTests +{ + + public class ExceptionTests : DebuggerTestBase + { + [Fact] + public async Task ExceptionTestAll() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + int line = 15; + int col = 20; + string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs"; + + await SetPauseOnException("all"); + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null); + //stop in the managed caught exception + pause_location = await WaitForManagedException(pause_location); + + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause0"); + + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = false + }), "exception0.data"); + + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented caught"); + + pause_location = await WaitForManagedException(null); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause1"); + + //stop in the uncaught exception + CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]); + + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented uncaught"); + }); + } + + [Fact] + public async Task JSExceptionTestAll() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetPauseOnException("all"); + + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null); + + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "TypeError", + uncaught = false + }), "exception0.data"); + + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception caught"); + + pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test"); + + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "RangeError", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception uncaught"); + }); + } + + // FIXME? BUG? We seem to get the stack trace for Runtime.exceptionThrown at `call_method`, + // but JS shows the original error type, and original trace + [Fact] + public async Task ExceptionTestNone() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetPauseOnException("none"); + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; + + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + var eo = JObject.Parse(ae.Message); + + // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value (), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); + + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "Error" // BUG?: "DebuggerTests.CustomException" + }), "exception"); + + return; + } + + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); + }); + } + + [Fact] + public async Task JSExceptionTestNone() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetPauseOnException("none"); + + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + + int line = 44; + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + Console.WriteLine($"{ae}"); + var eo = JObject.Parse(ae.Message); + + AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value(), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); + + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "RangeError" + }), "exception"); + + return; + } + + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); + }); + } + + [Theory] + [InlineData("function () { exceptions_test (); }", null, 0, 0, "exception_uncaught_test", "RangeError", "exception uncaught")] + [InlineData("function () { invoke_static_method ('[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions'); }", + "dotnet://debugger-test.dll/debugger-exception-test.cs", 28, 16, "run", + "DebuggerTests.CustomException", "not implemented uncaught")] + public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name, + string exception_type, string exception_message) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetPauseOnException("uncaught"); + + var eval_expr = $"window.setTimeout({eval_fn}, 1);"; + var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name); + + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = exception_type, + uncaught = true + }), "exception.data"); + + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", exception_message); + }); + } + + async Task WaitForManagedException(JObject pause_location) + { + while (true) + { + if (pause_location != null) + { + AssertEqual("exception", pause_location["reason"]?.Value(), $"Expected to only pause because of an exception. {pause_location}"); + + // return in case of a managed exception, and ignore JS ones + if (pause_location["data"]?["objectId"]?.Value()?.StartsWith("dotnet:object:") == true) + { + break; + } + } + + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", null, 0, 0, null); + } + + return pause_location; + } + } +} diff --git a/sdks/wasm/DebuggerTestSuite/InspectorClient.cs b/sdks/wasm/DebuggerTestSuite/InspectorClient.cs index 84b9414d9c6c..7a8e1c871680 100644 --- a/sdks/wasm/DebuggerTestSuite/InspectorClient.cs +++ b/sdks/wasm/DebuggerTestSuite/InspectorClient.cs @@ -1,74 +1,81 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using System.Threading; -using System.Text; +using System; using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; -namespace WebAssembly.Net.Debugging { - internal class InspectorClient : DevToolsClient { - List<(int, TaskCompletionSource)> pending_cmds = new List<(int, TaskCompletionSource)> (); - Func onEvent; - int next_cmd_id; +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class InspectorClient : DevToolsClient + { + List<(int, TaskCompletionSource)> pending_cmds = new List<(int, TaskCompletionSource)>(); + Func onEvent; + int next_cmd_id; - public InspectorClient (ILogger logger) : base(logger) {} + public InspectorClient(ILogger logger) : base(logger) { } - Task HandleMessage (string msg, CancellationToken token) - { - var res = JObject.Parse (msg); - if (res ["id"] == null) - DumpProtocol (string.Format("Event method: {0} params: {1}", res ["method"], res ["params"])); - else - DumpProtocol (string.Format ("Response id: {0} res: {1}", res ["id"], res)); + Task HandleMessage(string msg, CancellationToken token) + { + var res = JObject.Parse(msg); + if (res["id"] == null) + DumpProtocol(string.Format("Event method: {0} params: {1}", res["method"], res["params"])); + else + DumpProtocol(string.Format("Response id: {0} res: {1}", res["id"], res)); - if (res ["id"] == null) - return onEvent (res ["method"].Value (), res ["params"] as JObject, token); - var id = res ["id"].Value (); - var idx = pending_cmds.FindIndex (e => e.Item1 == id); - var item = pending_cmds [idx]; - pending_cmds.RemoveAt (idx); - item.Item2.SetResult (Result.FromJson (res)); - return null; - } + if (res["id"] == null) + return onEvent(res["method"].Value(), res["params"] as JObject, token); + var id = res["id"].Value(); + var idx = pending_cmds.FindIndex(e => e.Item1 == id); + var item = pending_cmds[idx]; + pending_cmds.RemoveAt(idx); + item.Item2.SetResult(Result.FromJson(res)); + return null; + } - public async Task Connect( - Uri uri, - Func onEvent, - Func send, - CancellationToken token) { + public async Task Connect( + Uri uri, + Func onEvent, + Func send, + CancellationToken token) + { - this.onEvent = onEvent; - await ConnectWithMainLoops (uri, HandleMessage, send, token); - } + this.onEvent = onEvent; + await ConnectWithMainLoops(uri, HandleMessage, send, token); + } - public Task SendCommand (string method, JObject args, CancellationToken token) - { - int id = ++next_cmd_id; - if (args == null) - args = new JObject (); + public Task SendCommand(string method, JObject args, CancellationToken token) + { + int id = ++next_cmd_id; + if (args == null) + args = new JObject(); - var o = JObject.FromObject (new { - id = id, - method = method, - @params = args - }); + var o = JObject.FromObject(new + { + id = id, + method = method, + @params = args + }); - var tcs = new TaskCompletionSource (); - pending_cmds.Add ((id, tcs)); + var tcs = new TaskCompletionSource(); + pending_cmds.Add((id, tcs)); - var str = o.ToString (); - //Log ("protocol", $"SendCommand: id: {id} method: {method} params: {args}"); + var str = o.ToString(); + //Log ("protocol", $"SendCommand: id: {id} method: {method} params: {args}"); - var bytes = Encoding.UTF8.GetBytes (str); - Send (bytes, token); - return tcs.Task; - } + var bytes = Encoding.UTF8.GetBytes(str); + Send(bytes, token); + return tcs.Task; + } - protected virtual void DumpProtocol (string msg){ - // Console.WriteLine (msg); - //XXX make logging not stupid - } - } -} + protected virtual void DumpProtocol(string msg) + { + // Console.WriteLine (msg); + //XXX make logging not stupid + } + } +} diff --git a/sdks/wasm/DebuggerTestSuite/PointerTests.cs b/sdks/wasm/DebuggerTestSuite/PointerTests.cs index 932f02a0c802..d18f054d9d78 100644 --- a/sdks/wasm/DebuggerTestSuite/PointerTests.cs +++ b/sdks/wasm/DebuggerTestSuite/PointerTests.cs @@ -1,520 +1,558 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; namespace DebuggerTests { - public class PointerTests : DebuggerTestBase { - - public static TheoryData PointersTestData => - new TheoryData { - {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false}, - {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true}, - {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false}, - {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true} - }; - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointersToPrimitiveTypes (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - ip = TPointer ("int*"), - ip_null = TPointer ("int*", is_null: true), - ipp = TPointer ("int**"), - ipp_null = TPointer ("int**"), - - cvalue0 = TSymbol ("113 'q'"), - cp = TPointer ("char*"), - - vp = TPointer ("void*"), - vp_null = TPointer ("void*", is_null: true), - }, "locals", num_fields: 26); - - var props = await GetObjectOnLocals (locals, "ip"); - await CheckPointerValue (props, "*ip", TNumber (5), "locals"); - - { - var ipp_props = await GetObjectOnLocals (locals, "ipp"); - await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*")); - - ipp_props = await GetObjectOnLocals (ipp_props, "*ipp"); - await CheckPointerValue (ipp_props, "**ipp", TNumber (5)); - } - - { - var ipp_props = await GetObjectOnLocals (locals, "ipp_null"); - await CheckPointerValue (ipp_props, "*ipp_null", TPointer ("int*", is_null: true)); - } - - // *cp - props = await GetObjectOnLocals (locals, "cp"); - await CheckPointerValue (props, "*cp", TSymbol ("113 'q'")); - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointerArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - ipa = TArray ("int*[]", 3) - }, "locals", num_fields: 26); - - var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] { - TPointer ("int*"), - TPointer ("int*"), - TPointer ("int*", is_null: true) - }); - - await CheckArrayElements (ipa_elems, new [] { - TNumber (5), - TNumber (10), - null - }); - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalDoublePointerToPrimitiveTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - ippa = TArray ("int**[]", 5) - }, "locals", num_fields: 26); - - var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] { - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**", is_null: true) - }); - - { - var actual_elems = await CheckArrayElements (ippa_elems, new [] { - TPointer ("int*"), - TPointer ("int*", is_null: true), - TPointer ("int*"), - TPointer ("int*", is_null: true), - null - }); - - var val = await GetObjectOnLocals (actual_elems [0], "*[0]"); - await CheckPointerValue (val, "**[0]", TNumber (5)); - - val = await GetObjectOnLocals (actual_elems [2], "*[2]"); - await CheckPointerValue (val, "**[2]", TNumber (5)); - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointersToValueTypes (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - dt = TValueType ("System.DateTime", dt.ToString ()), - dtp = TPointer ("System.DateTime*"), - dtp_null = TPointer ("System.DateTime*", is_null: true), - - gsp = TPointer ("DebuggerTests.GenericStructWithUnmanagedT*"), - gsp_null = TPointer ("DebuggerTests.GenericStructWithUnmanagedT*") - }, "locals", num_fields: 26); - - await CheckDateTime (locals, "dt", dt); - - // *dtp - var props = await GetObjectOnLocals (locals, "dtp"); - await CheckDateTime (props, "*dtp", dt); - - var gsp_props = await GetObjectOnLocals (locals, "gsp"); - await CheckPointerValue (gsp_props, "*gsp", TValueType ("DebuggerTests.GenericStructWithUnmanagedT"), "locals#gsp"); - - { - var gs_dt = new DateTime (1, 2, 3, 4, 5, 6); - - var gsp_deref_props = await GetObjectOnLocals (gsp_props, "*gsp"); - await CheckProps (gsp_deref_props, new { - Value = TValueType ("System.DateTime", gs_dt.ToString ()), - IntField = TNumber (4), - DTPP = TPointer ("System.DateTime**") - }, "locals#gsp#deref"); - { - var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP"); - await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp"); - - var dtpp_deref_props = await GetObjectOnLocals (dtpp_props, "*DTPP"); - await CheckDateTime (dtpp_deref_props, "**DTPP", dt); - } - } - - // gsp_null - var gsp_w_n_props = await GetObjectOnLocals (locals, "gsp_null"); - await CheckPointerValue (gsp_w_n_props, "*gsp_null", TValueType ("DebuggerTests.GenericStructWithUnmanagedT"), "locals#gsp"); - - { - var gs_dt = new DateTime (1, 2, 3, 4, 5, 6); - - var gsp_deref_props = await GetObjectOnLocals (gsp_w_n_props, "*gsp_null"); - await CheckProps (gsp_deref_props, new { - Value = TValueType ("System.DateTime", gs_dt.ToString ()), - IntField = TNumber (4), - DTPP = TPointer ("System.DateTime**") - }, "locals#gsp#deref"); - { - var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP"); - await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp"); - } - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointersToValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - dtpa = TArray ("System.DateTime*[]", 2) - }, "locals", num_fields: 26); - - // dtpa - var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] { - TPointer ("System.DateTime*"), - TPointer ("System.DateTime*", is_null: true) - })); - { - var actual_elems = await CheckArrayElements (dtpa_elems, new [] { - TValueType ("System.DateTime", dt.ToString ()), - null - }); - - await CheckDateTime (actual_elems [0], "*[0]", dt); - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointersToGenericValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - gspa = TArray ("DebuggerTests.GenericStructWithUnmanagedT*[]", 3), - }, "locals", num_fields: 26); - - // dtpa - var gspa_elems = await CompareObjectPropertiesFor (locals, "gspa", new [] { - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*", is_null: true), - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*"), - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*"), - }); - { - var gs_dt = new DateTime (1, 2, 3, 4, 5, 6); - var actual_elems = await CheckArrayElements (gspa_elems, new [] { - null, - TValueType ("DebuggerTests.GenericStructWithUnmanagedT"), - TValueType ("DebuggerTests.GenericStructWithUnmanagedT") - }); - - // *[1] - { - var gsp_deref_props = await GetObjectOnLocals (actual_elems [1], "*[1]"); - await CheckProps (gsp_deref_props, new { - Value = TValueType ("System.DateTime", gs_dt.ToString ()), - IntField = TNumber (4), - DTPP = TPointer ("System.DateTime**") - }, "locals#gsp#deref"); - { - var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP"); - await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp"); - - dtpp_props = await GetObjectOnLocals (dtpp_props, "*DTPP"); - await CheckDateTime (dtpp_props, "**DTPP", dt); - } - } - - // *[2] - { - var gsp_deref_props = await GetObjectOnLocals (actual_elems [2], "*[2]"); - await CheckProps (gsp_deref_props, new { - Value = TValueType ("System.DateTime", gs_dt.ToString ()), - IntField = TNumber (4), - DTPP = TPointer ("System.DateTime**") - }, "locals#gsp#deref"); - { - var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP"); - await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp"); - } - } - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalDoublePointersToValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - dtppa = TArray ("System.DateTime**[]", 3), - }, "locals", num_fields: 26); - - // DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; - var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] { - TPointer ("System.DateTime**"), - TPointer ("System.DateTime**"), - TPointer ("System.DateTime**", is_null: true) - })); - - var exp_elems = new [] { - TPointer ("System.DateTime*"), - TPointer ("System.DateTime*", is_null: true), - null - }; - - var actual_elems = new JToken [exp_elems.Length]; - for (int i = 0; i < exp_elems.Length; i ++) { - if (exp_elems [i] != null) { - actual_elems [i] = await GetObjectOnLocals (dtppa_elems, i.ToString ()); - await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->"); - } - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersTestData))] - public async Task InspectLocalPointersInClasses (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - cwp = TObject ("DebuggerTests.GenericClassWithPointers"), - cwp_null = TObject ("DebuggerTests.GenericClassWithPointers") - }, "locals", num_fields: 26); - - var cwp_props = await GetObjectOnLocals (locals, "cwp"); - var ptr_props = await GetObjectOnLocals (cwp_props, "Ptr"); - await CheckDateTime (ptr_props, "*Ptr", dt); - }); - - public static TheoryData PointersAsMethodArgsTestData => - new TheoryData { - {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false}, - {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true}, - }; - - [Theory] - [MemberDataAttribute (nameof (PointersAsMethodArgsTestData))] - public async Task InspectPrimitiveTypePointersAsMethodArgs (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - ip = TPointer ("int*"), - ipp = TPointer ("int**"), - ipa = TArray ("int*[]", 3), - ippa = TArray ("int**[]", 5) - }, "locals", num_fields: 8); - - // ip - var props = await GetObjectOnLocals (locals, "ip"); - await CheckPointerValue (props, "*ip", TNumber (5), "locals"); - - // ipp - var ipp_props = await GetObjectOnLocals (locals, "ipp"); - await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*")); - - ipp_props = await GetObjectOnLocals (ipp_props, "*ipp"); - await CheckPointerValue (ipp_props, "**ipp", TNumber (5)); - - // ipa - var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] { - TPointer ("int*"), - TPointer ("int*"), - TPointer ("int*", is_null: true) - }); - - await CheckArrayElements (ipa_elems, new [] { - TNumber (5), - TNumber (10), - null - }); - - // ippa - var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] { - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**"), - TPointer ("int**", is_null: true) - }); - - { - var actual_elems = await CheckArrayElements (ippa_elems, new [] { - TPointer ("int*"), - TPointer ("int*", is_null: true), - TPointer ("int*"), - TPointer ("int*", is_null: true), - null - }); - - var val = await GetObjectOnLocals (actual_elems [0], "*[0]"); - await CheckPointerValue (val, "**[0]", TNumber (5)); - - val = await GetObjectOnLocals (actual_elems [2], "*[2]"); - await CheckPointerValue (val, "**[2]", TNumber (5)); - } - }); - - [Theory] - [MemberDataAttribute (nameof (PointersAsMethodArgsTestData))] - public async Task InspectValueTypePointersAsMethodArgs (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - dtp = TPointer ("System.DateTime*"), - dtpp = TPointer ("System.DateTime**"), - dtpa = TArray ("System.DateTime*[]", 2), - dtppa = TArray ("System.DateTime**[]", 3) - }, "locals", num_fields: 8); - - // *dtp - var dtp_props = await GetObjectOnLocals (locals, "dtp"); - await CheckDateTime (dtp_props, "*dtp", dt); - - // *dtpp - var dtpp_props = await GetObjectOnLocals (locals, "dtpp"); - await CheckPointerValue (dtpp_props, "*dtpp", TPointer ("System.DateTime*"), "locals"); - - dtpp_props = await GetObjectOnLocals (dtpp_props, "*dtpp"); - await CheckDateTime (dtpp_props, "**dtpp", dt); - - // dtpa - var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] { - TPointer ("System.DateTime*"), - TPointer ("System.DateTime*", is_null: true) - })); - { - var actual_elems = await CheckArrayElements (dtpa_elems, new [] { - TValueType ("System.DateTime", dt.ToString ()), - null - }); - - await CheckDateTime (actual_elems [0], "*[0]", dt); - } - - // dtppa = new DateTime**[] { &dtp, &dtp_null, null }; - var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] { - TPointer ("System.DateTime**"), - TPointer ("System.DateTime**"), - TPointer ("System.DateTime**", is_null: true) - })); - - var exp_elems = new [] { - TPointer ("System.DateTime*"), - TPointer ("System.DateTime*", is_null: true), - null - }; - - await CheckArrayElements (dtppa_elems, exp_elems); - }); - - [Theory] - [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)] - [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)] - public async Task DerefNonPointerObject (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - type, method, line_offset, bp_function_name, - "window.setTimeout(function() { " + eval_fn + " })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - - // this will generate the object ids - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - var complex = GetAndAssertObjectWithName (locals, "complex"); - - // try to deref the non-pointer object, as a pointer - var props = await GetProperties (complex ["value"]["objectId"].Value ().Replace (":object:", ":pointer:")); - Assert.Empty (props.Values ()); - - // try to deref an invalid pointer id - props = await GetProperties ("dotnet:pointer:123897"); - Assert.Empty (props.Values ()); - }); - - async Task CheckArrayElements (JToken array, JToken[] exp_elems) - { - var actual_elems = new JToken [exp_elems.Length]; - for (int i = 0; i < exp_elems.Length; i ++) { - if (exp_elems [i] != null) { - actual_elems [i] = await GetObjectOnLocals (array, i.ToString ()); - await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->"); - } - } - - return actual_elems; - } - } -} \ No newline at end of file + public class PointerTests : DebuggerTestBase + { + + public static TheoryData PointersTestData => + new TheoryData + { { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false }, + { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true }, + { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false }, + { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true } + }; + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointersToPrimitiveTypes(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + ip = TPointer("int*"), + ip_null = TPointer("int*", is_null: true), + ipp = TPointer("int**"), + ipp_null = TPointer("int**"), + + cvalue0 = TSymbol("113 'q'"), + cp = TPointer("char*"), + + vp = TPointer("void*"), + vp_null = TPointer("void*", is_null: true), + }, "locals", num_fields: 26); + + var props = await GetObjectOnLocals(locals, "ip"); + await CheckPointerValue(props, "*ip", TNumber(5), "locals"); + + { + var ipp_props = await GetObjectOnLocals(locals, "ipp"); + await CheckPointerValue(ipp_props, "*ipp", TPointer("int*")); + + ipp_props = await GetObjectOnLocals(ipp_props, "*ipp"); + await CheckPointerValue(ipp_props, "**ipp", TNumber(5)); + } + + { + var ipp_props = await GetObjectOnLocals(locals, "ipp_null"); + await CheckPointerValue(ipp_props, "*ipp_null", TPointer("int*", is_null: true)); + } + + // *cp + props = await GetObjectOnLocals(locals, "cp"); + await CheckPointerValue(props, "*cp", TSymbol("113 'q'")); + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointerArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + ipa = TArray("int*[]", 3) + }, "locals", num_fields: 26); + + var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new[] + { + TPointer("int*"), + TPointer("int*"), + TPointer("int*", is_null : true) + }); + + await CheckArrayElements(ipa_elems, new[] + { + TNumber(5), + TNumber(10), + null + }); + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalDoublePointerToPrimitiveTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + ippa = TArray("int**[]", 5) + }, "locals", num_fields: 26); + + var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new[] + { + TPointer("int**"), + TPointer("int**"), + TPointer("int**"), + TPointer("int**"), + TPointer("int**", is_null : true) + }); + + { + var actual_elems = await CheckArrayElements(ippa_elems, new[] + { + TPointer("int*"), + TPointer("int*", is_null : true), + TPointer("int*"), + TPointer("int*", is_null : true), + null + }); + + var val = await GetObjectOnLocals(actual_elems[0], "*[0]"); + await CheckPointerValue(val, "**[0]", TNumber(5)); + + val = await GetObjectOnLocals(actual_elems[2], "*[2]"); + await CheckPointerValue(val, "**[2]", TNumber(5)); + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + dt = TValueType("System.DateTime", dt.ToString()), + dtp = TPointer("System.DateTime*"), + dtp_null = TPointer("System.DateTime*", is_null: true), + + gsp = TPointer("DebuggerTests.GenericStructWithUnmanagedT*"), + gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT*") + }, "locals", num_fields: 26); + + await CheckDateTime(locals, "dt", dt); + + // *dtp + var props = await GetObjectOnLocals(locals, "dtp"); + await CheckDateTime(props, "*dtp", dt); + + var gsp_props = await GetObjectOnLocals(locals, "gsp"); + await CheckPointerValue(gsp_props, "*gsp", TValueType("DebuggerTests.GenericStructWithUnmanagedT"), "locals#gsp"); + + { + var gs_dt = new DateTime(1, 2, 3, 4, 5, 6); + + var gsp_deref_props = await GetObjectOnLocals(gsp_props, "*gsp"); + await CheckProps(gsp_deref_props, new + { + Value = TValueType("System.DateTime", gs_dt.ToString()), + IntField = TNumber(4), + DTPP = TPointer("System.DateTime**") + }, "locals#gsp#deref"); + { + var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP"); + await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp"); + + var dtpp_deref_props = await GetObjectOnLocals(dtpp_props, "*DTPP"); + await CheckDateTime(dtpp_deref_props, "**DTPP", dt); + } + } + + // gsp_null + var gsp_w_n_props = await GetObjectOnLocals(locals, "gsp_null"); + await CheckPointerValue(gsp_w_n_props, "*gsp_null", TValueType("DebuggerTests.GenericStructWithUnmanagedT"), "locals#gsp"); + + { + var gs_dt = new DateTime(1, 2, 3, 4, 5, 6); + + var gsp_deref_props = await GetObjectOnLocals(gsp_w_n_props, "*gsp_null"); + await CheckProps(gsp_deref_props, new + { + Value = TValueType("System.DateTime", gs_dt.ToString()), + IntField = TNumber(4), + DTPP = TPointer("System.DateTime**") + }, "locals#gsp#deref"); + { + var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP"); + await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null: true), "locals#*gsp"); + } + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + dtpa = TArray("System.DateTime*[]", 2) + }, "locals", num_fields: 26); + + // dtpa + var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new[] + { + TPointer("System.DateTime*"), + TPointer("System.DateTime*", is_null : true) + })); + { + var actual_elems = await CheckArrayElements(dtpa_elems, new[] + { + TValueType("System.DateTime", dt.ToString()), + null + }); + + await CheckDateTime(actual_elems[0], "*[0]", dt); + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointersToGenericValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + gspa = TArray("DebuggerTests.GenericStructWithUnmanagedT*[]", 3), + }, "locals", num_fields: 26); + + // dtpa + var gspa_elems = await CompareObjectPropertiesFor(locals, "gspa", new[] + { + TPointer("DebuggerTests.GenericStructWithUnmanagedT*", is_null : true), + TPointer("DebuggerTests.GenericStructWithUnmanagedT*"), + TPointer("DebuggerTests.GenericStructWithUnmanagedT*"), + }); + { + var gs_dt = new DateTime(1, 2, 3, 4, 5, 6); + var actual_elems = await CheckArrayElements(gspa_elems, new[] + { + null, + TValueType("DebuggerTests.GenericStructWithUnmanagedT"), + TValueType("DebuggerTests.GenericStructWithUnmanagedT") + }); + + // *[1] + { + var gsp_deref_props = await GetObjectOnLocals(actual_elems[1], "*[1]"); + await CheckProps(gsp_deref_props, new + { + Value = TValueType("System.DateTime", gs_dt.ToString()), + IntField = TNumber(4), + DTPP = TPointer("System.DateTime**") + }, "locals#gsp#deref"); + { + var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP"); + await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp"); + + dtpp_props = await GetObjectOnLocals(dtpp_props, "*DTPP"); + await CheckDateTime(dtpp_props, "**DTPP", dt); + } + } + + // *[2] + { + var gsp_deref_props = await GetObjectOnLocals(actual_elems[2], "*[2]"); + await CheckProps(gsp_deref_props, new + { + Value = TValueType("System.DateTime", gs_dt.ToString()), + IntField = TNumber(4), + DTPP = TPointer("System.DateTime**") + }, "locals#gsp#deref"); + { + var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP"); + await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null: true), "locals#*gsp"); + } + } + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalDoublePointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + dtppa = TArray("System.DateTime**[]", 3), + }, "locals", num_fields: 26); + + // DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; + var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new[] + { + TPointer("System.DateTime**"), + TPointer("System.DateTime**"), + TPointer("System.DateTime**", is_null : true) + })); + + var exp_elems = new[] + { + TPointer("System.DateTime*"), + TPointer("System.DateTime*", is_null : true), + null + }; + + var actual_elems = new JToken[exp_elems.Length]; + for (int i = 0; i < exp_elems.Length; i++) + { + if (exp_elems[i] != null) + { + actual_elems[i] = await GetObjectOnLocals(dtppa_elems, i.ToString()); + await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->"); + } + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersTestData))] + public async Task InspectLocalPointersInClasses(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + cwp = TObject("DebuggerTests.GenericClassWithPointers"), + cwp_null = TObject("DebuggerTests.GenericClassWithPointers") + }, "locals", num_fields: 26); + + var cwp_props = await GetObjectOnLocals(locals, "cwp"); + var ptr_props = await GetObjectOnLocals(cwp_props, "Ptr"); + await CheckDateTime(ptr_props, "*Ptr", dt); + }); + + public static TheoryData PointersAsMethodArgsTestData => + new TheoryData + { { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false }, + { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true }, + }; + + [Theory] + [MemberDataAttribute(nameof(PointersAsMethodArgsTestData))] + public async Task InspectPrimitiveTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + ip = TPointer("int*"), + ipp = TPointer("int**"), + ipa = TArray("int*[]", 3), + ippa = TArray("int**[]", 5) + }, "locals", num_fields: 8); + + // ip + var props = await GetObjectOnLocals(locals, "ip"); + await CheckPointerValue(props, "*ip", TNumber(5), "locals"); + + // ipp + var ipp_props = await GetObjectOnLocals(locals, "ipp"); + await CheckPointerValue(ipp_props, "*ipp", TPointer("int*")); + + ipp_props = await GetObjectOnLocals(ipp_props, "*ipp"); + await CheckPointerValue(ipp_props, "**ipp", TNumber(5)); + + // ipa + var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new[] + { + TPointer("int*"), + TPointer("int*"), + TPointer("int*", is_null : true) + }); + + await CheckArrayElements(ipa_elems, new[] + { + TNumber(5), + TNumber(10), + null + }); + + // ippa + var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new[] + { + TPointer("int**"), + TPointer("int**"), + TPointer("int**"), + TPointer("int**"), + TPointer("int**", is_null : true) + }); + + { + var actual_elems = await CheckArrayElements(ippa_elems, new[] + { + TPointer("int*"), + TPointer("int*", is_null : true), + TPointer("int*"), + TPointer("int*", is_null : true), + null + }); + + var val = await GetObjectOnLocals(actual_elems[0], "*[0]"); + await CheckPointerValue(val, "**[0]", TNumber(5)); + + val = await GetObjectOnLocals(actual_elems[2], "*[2]"); + await CheckPointerValue(val, "**[2]", TNumber(5)); + } + }); + + [Theory] + [MemberDataAttribute(nameof(PointersAsMethodArgsTestData))] + public async Task InspectValueTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + dtp = TPointer("System.DateTime*"), + dtpp = TPointer("System.DateTime**"), + dtpa = TArray("System.DateTime*[]", 2), + dtppa = TArray("System.DateTime**[]", 3) + }, "locals", num_fields: 8); + + // *dtp + var dtp_props = await GetObjectOnLocals(locals, "dtp"); + await CheckDateTime(dtp_props, "*dtp", dt); + + // *dtpp + var dtpp_props = await GetObjectOnLocals(locals, "dtpp"); + await CheckPointerValue(dtpp_props, "*dtpp", TPointer("System.DateTime*"), "locals"); + + dtpp_props = await GetObjectOnLocals(dtpp_props, "*dtpp"); + await CheckDateTime(dtpp_props, "**dtpp", dt); + + // dtpa + var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new[] + { + TPointer("System.DateTime*"), + TPointer("System.DateTime*", is_null : true) + })); + { + var actual_elems = await CheckArrayElements(dtpa_elems, new[] + { + TValueType("System.DateTime", dt.ToString()), + null + }); + + await CheckDateTime(actual_elems[0], "*[0]", dt); + } + + // dtppa = new DateTime**[] { &dtp, &dtp_null, null }; + var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new[] + { + TPointer("System.DateTime**"), + TPointer("System.DateTime**"), + TPointer("System.DateTime**", is_null : true) + })); + + var exp_elems = new[] + { + TPointer("System.DateTime*"), + TPointer("System.DateTime*", is_null : true), + null + }; + + await CheckArrayElements(dtppa_elems, exp_elems); + }); + + [Theory] + [InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)] + [InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)] + public async Task DerefNonPointerObject(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + type, method, line_offset, bp_function_name, + "window.setTimeout(function() { " + eval_fn + " })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + + // this will generate the object ids + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + var complex = GetAndAssertObjectWithName(locals, "complex"); + + // try to deref the non-pointer object, as a pointer + await GetProperties(complex["value"]["objectId"].Value().Replace(":object:", ":pointer:"), expect_ok: false); + + // try to deref an invalid pointer id + await GetProperties("dotnet:pointer:123897", expect_ok: false); + }); + + async Task CheckArrayElements(JToken array, JToken[] exp_elems) + { + var actual_elems = new JToken[exp_elems.Length]; + for (int i = 0; i < exp_elems.Length; i++) + { + if (exp_elems[i] != null) + { + actual_elems[i] = await GetObjectOnLocals(array, i.ToString()); + await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->"); + } + } + + return actual_elems; + } + } +} diff --git a/sdks/wasm/DebuggerTestSuite/Support.cs b/sdks/wasm/DebuggerTestSuite/Support.cs index efa161090877..81501e443d13 100644 --- a/sdks/wasm/DebuggerTestSuite/Support.cs +++ b/sdks/wasm/DebuggerTestSuite/Support.cs @@ -1,915 +1,1064 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; +using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Threading.Tasks; - using System.Net.WebSockets; -using System.Threading; -using System.IO; using System.Text; -using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using WebAssembly.Net.Debugging; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; +using Xunit.Sdk; namespace DebuggerTests { - class Inspector - { - // InspectorClient client; - Dictionary> notifications = new Dictionary> (); - Dictionary> eventListeners = new Dictionary> (); - - public const string PAUSE = "pause"; - public const string READY = "ready"; - - public Task WaitFor(string what) { - if (notifications.ContainsKey (what)) - throw new Exception ($"Invalid internal state, waiting for {what} while another wait is already setup"); - var n = new TaskCompletionSource (); - notifications [what] = n; - return n.Task; - } - - void NotifyOf (string what, JObject args) { - if (!notifications.ContainsKey (what)) - throw new Exception ($"Invalid internal state, notifying of {what}, but nobody waiting"); - notifications [what].SetResult (args); - notifications.Remove (what); - } - - public void On(string evtName, Func cb) { - eventListeners[evtName] = cb; - } - - void FailAllWaitersWithException (JObject exception) - { - foreach (var tcs in notifications.Values) - tcs.SetException (new ArgumentException (exception.ToString ())); - } - - async Task OnMessage(string method, JObject args, CancellationToken token) - { - //System.Console.WriteLine("OnMessage " + method + args); - switch (method) { - case "Debugger.paused": - NotifyOf (PAUSE, args); - break; - case "Mono.runtimeReady": - NotifyOf (READY, args); - break; - case "Runtime.consoleAPICalled": - Console.WriteLine ("CWL: {0}", args? ["args"]? [0]? ["value"]); - break; - } - if (eventListeners.ContainsKey (method)) - await eventListeners[method](args, token); - else if (String.Compare (method, "Runtime.exceptionThrown") == 0) - FailAllWaitersWithException (args); - } - - public async Task Ready (Func cb = null, TimeSpan? span = null) { - using (var cts = new CancellationTokenSource ()) { - cts.CancelAfter (span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default - var uri = new Uri ($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect"); - using var loggerFactory = LoggerFactory.Create( - builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - using (var client = new InspectorClient (loggerFactory.CreateLogger())) { - await client.Connect (uri, OnMessage, async token => { - Task[] init_cmds = { - client.SendCommand ("Profiler.enable", null, token), - client.SendCommand ("Runtime.enable", null, token), - client.SendCommand ("Debugger.enable", null, token), - client.SendCommand ("Runtime.runIfWaitingForDebugger", null, token), - WaitFor (READY), - }; - // await Task.WhenAll (init_cmds); - Console.WriteLine ("waiting for the runtime to be ready"); - await init_cmds [4]; - Console.WriteLine ("runtime ready, TEST TIME"); - if (cb != null) { - Console.WriteLine("await cb(client, token)"); - await cb(client, token); - } - - }, cts.Token); - await client.Close (cts.Token); - } - } - } - } - - public class DebuggerTestBase { - protected Task startTask; - - static string FindTestPath () { - //FIXME how would I locate it otherwise? - var test_path = Environment.GetEnvironmentVariable ("TEST_SUITE_PATH"); - //Lets try to guest - if (test_path != null && Directory.Exists (test_path)) - return test_path; - - var cwd = Environment.CurrentDirectory; - Console.WriteLine ("guessing from {0}", cwd); - //tests run from DebuggerTestSuite/bin/Debug/netcoreapp2.1 - var new_path = Path.Combine (cwd, "../../../../bin/debugger-test-suite"); - if (File.Exists (Path.Combine (new_path, "debugger-driver.html"))) - return new_path; - - throw new Exception ("Missing TEST_SUITE_PATH env var and could not guess path from CWD"); - } - - static string[] PROBE_LIST = { - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", - "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", - "/usr/bin/chromium", - "/usr/bin/chromium-browser", - }; - static string chrome_path; - - static string FindChromePath () - { - if (chrome_path != null) - return chrome_path; - - foreach (var s in PROBE_LIST){ - if (File.Exists (s)) { - chrome_path = s; - Console.WriteLine($"Using chrome path: ${s}"); - return s; - } - } - throw new Exception ("Could not find an installed Chrome to use"); - } - - public DebuggerTestBase (string driver = "debugger-driver.html") { - startTask = TestHarnessProxy.Start (FindChromePath (), FindTestPath (), driver); - } - - public Task Ready () - => startTask; - - internal DebugTestContext ctx; - internal Dictionary dicScriptsIdToUrl; - internal Dictionary dicFileToUrl; - internal Dictionary SubscribeToScripts (Inspector insp) { - dicScriptsIdToUrl = new Dictionary (); - dicFileToUrl = new Dictionary(); - insp.On("Debugger.scriptParsed", async (args, c) => { - var script_id = args? ["scriptId"]?.Value (); - var url = args["url"]?.Value (); - if (script_id.StartsWith("dotnet://")) - { - var dbgUrl = args["dotNetUrl"]?.Value(); - var arrStr = dbgUrl.Split("/"); - dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; - dicScriptsIdToUrl[script_id] = dbgUrl; - dicFileToUrl[dbgUrl] = args["url"]?.Value(); - } else if (!String.IsNullOrEmpty (url)) { - dicFileToUrl[new Uri (url).AbsolutePath] = url; - } - await Task.FromResult (0); - }); - return dicScriptsIdToUrl; - } - - internal async Task CheckInspectLocalsAtBreakpointSite (string url_key, int line, int column, string function_name, string eval_expression, - Action test_fn = null, Func wait_for_event_fn = null, bool use_cfo = false) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - var bp = await SetBreakpoint (url_key, line, column); - - await EvaluateAndCheck ( - eval_expression, url_key, line, column, - function_name, - wait_for_event_fn: async (pause_location) => { - //make sure we're on the right bp - - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - - var scope = top_frame ["scopeChain"][0]; - Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]); - if (wait_for_event_fn != null) - await wait_for_event_fn(pause_location); - else - await Task.CompletedTask; - }, - locals_fn: (locals) => { - if (test_fn != null) - test_fn (locals); - } - ); - }); - } - - // sets breakpoint by method name and line offset - internal async Task CheckInspectLocalsAtBreakpointSite (string type, string method, int line_offset, string bp_function_name, string eval_expression, - Action locals_fn = null, Func wait_for_event_fn = null, bool use_cfo = false, string assembly="debugger-test.dll", int col = 0) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - var bp = await SetBreakpointInMethod (assembly, type, method, line_offset, col); - - var args = JObject.FromObject (new { expression = eval_expression }); - var res = await ctx.cli.SendCommand ("Runtime.evaluate", args, ctx.token); - if (!res.IsOk) { - Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}"); - Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}"); - } - - var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE); - - if (bp_function_name != null) - Assert.Equal (bp_function_name, pause_location ["callFrames"]?[0]?["functionName"]?.Value ()); - - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - - var scope = top_frame ["scopeChain"][0]; - Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]); - - if (wait_for_event_fn != null) - await wait_for_event_fn (pause_location); - - if (locals_fn != null) { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - locals_fn (locals); - } - }); - } - - internal void CheckLocation (string script_loc, int line, int column, Dictionary scripts, JToken location) - { - var loc_str = $"{ scripts[location["scriptId"].Value()] }" - + $"#{ location ["lineNumber"].Value () }" - + $"#{ location ["columnNumber"].Value () }"; - - var expected_loc_str = $"{script_loc}#{line}#{column}"; - Assert.Equal (expected_loc_str, loc_str); - } - - internal void CheckNumber (JToken locals, string name, T value) { - foreach (var l in locals) { - if (name != l["name"]?.Value ()) - continue; - var val = l["value"]; - Assert.Equal ("number", val ["type"]?.Value ()); - Assert.Equal (value, val["value"].Value ()); - return; - } - Assert.True(false, $"Could not find variable '{name}'"); - } - - internal void CheckString (JToken locals, string name, string value) { - foreach (var l in locals) { - if (name != l["name"]?.Value ()) - continue; - var val = l["value"]; - if (value == null) { - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.Equal ("null", val["subtype"]?.Value ()); - } else { - Assert.Equal ("string", val ["type"]?.Value ()); - Assert.Equal (value, val["value"]?.Value ()); - } - return; - } - Assert.True(false, $"Could not find variable '{name}'"); - } - - internal JToken CheckSymbol (JToken locals, string name, string value) - { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - Assert.Equal ("symbol", val ["type"]?.Value ()); - Assert.Equal (value, val ["value"]?.Value ()); - return l; - } - - internal JToken CheckObject (JToken locals, string name, string class_name, string subtype=null, bool is_null=false) { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.True (val ["isValueType"] == null || !val ["isValueType"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - - var has_null_subtype = val ["subtype"] != null && val ["subtype"]?.Value () == "null"; - Assert.Equal (is_null, has_null_subtype); - if (subtype != null) - Assert.Equal (subtype, val ["subtype"]?.Value ()); - - return l; - } - - internal async Task CheckPointerValue (JToken locals, string name, JToken expected, string label = null) - { - var l = GetAndAssertObjectWithName (locals, name); - await CheckValue (l ["value"], expected, $"{label ?? String.Empty}-{name}"); - return l; - } - - internal async Task CheckDateTime (JToken locals, string name, DateTime expected) - { - var obj = GetAndAssertObjectWithName(locals, name); - await CheckDateTimeValue (obj ["value"], expected); - } - - internal async Task CheckDateTimeValue (JToken value, DateTime expected) - { - AssertEqual ("System.DateTime", value ["className"]?.Value (), "className"); - AssertEqual (expected.ToString (), value ["description"]?.Value (), "description"); - - var members = await GetProperties (value ["objectId"]?.Value ()); - - // not checking everything - CheckNumber (members, "Year", expected.Year); - CheckNumber (members, "Month", expected.Month); - CheckNumber (members, "Day", expected.Day); - CheckNumber (members, "Hour", expected.Hour); - CheckNumber (members, "Minute", expected.Minute); - CheckNumber (members, "Second", expected.Second); - - // FIXME: check some float properties too - } - - internal JToken CheckBool (JToken locals, string name, bool expected) - { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - Assert.Equal ("boolean", val ["type"]?.Value ()); - if (val ["value"] == null) - Assert.True (false, "expected bool value not found for variable named {name}"); - Assert.Equal (expected, val ["value"]?.Value ()); - - return l; - } - - internal void CheckContentValue (JToken token, string value) { - var val = token["value"].Value (); - Assert.Equal (value, val); - } - - internal JToken CheckValueType (JToken locals, string name, string class_name) { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.True (val ["isValueType"] != null && val ["isValueType"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - return l; - } - - internal JToken CheckEnum (JToken locals, string name, string class_name, string descr) { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.True (val ["isEnum"] != null && val ["isEnum"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - Assert.Equal (descr, val ["description"]?.Value ()); - return l; - } - - internal void CheckArray (JToken locals, string name, string class_name) { - foreach (var l in locals) { - if (name != l["name"]?.Value ()) - continue; - - var val = l["value"]; - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.Equal ("array", val ["subtype"]?.Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - - //FIXME: elements? - return; - } - Assert.True(false, $"Could not find variable '{name}'"); - } - - internal JToken GetAndAssertObjectWithName (JToken obj, string name) - { - var l = obj.FirstOrDefault (jt => jt ["name"]?.Value () == name); - if (l == null) - Assert.True (false, $"Could not find variable '{name}'"); - return l; - } - - internal async Task SendCommand (string method, JObject args) { - var res = await ctx.cli.SendCommand (method, args, ctx.token); - if (!res.IsOk) { - Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}"); - Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}"); - } - return res; - } - - internal async Task Evaluate (string expression) { - return await SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = expression })); - } - - internal void AssertLocation (JObject args, string methodName) { - Assert.Equal (methodName, args ["callFrames"]?[0]?["functionName"]?.Value ()); - } - - // Place a breakpoint in the given method and run until its hit - // Return the Debugger.paused data - internal async Task RunUntil (string methodName) { - await SetBreakpointInMethod ("debugger-test", "DebuggerTest", methodName); - // This will run all the tests until it hits the bp - await Evaluate ("window.setTimeout(function() { invoke_run_all (); }, 1);"); - var wait_res = await ctx.insp.WaitFor (Inspector.PAUSE); - AssertLocation (wait_res, "locals_inner"); - return wait_res; - } - - internal async Task StepAndCheck (StepKind kind, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action locals_fn = null, int times=1) - { - for (int i = 0; i < times - 1; i ++) { - await SendCommandAndCheck (null, $"Debugger.step{kind.ToString ()}", null, -1, -1, null); - } - - // Check for method/line etc only at the last step - return await SendCommandAndCheck ( - null, $"Debugger.step{kind.ToString ()}", script_loc, line, column, function_name, - wait_for_event_fn: wait_for_event_fn, - locals_fn: locals_fn); - } - - internal async Task EvaluateAndCheck (string expression, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action locals_fn = null) - => await SendCommandAndCheck ( - JObject.FromObject (new { expression = expression }), - "Runtime.evaluate", script_loc, line, column, function_name, - wait_for_event_fn: wait_for_event_fn, - locals_fn: locals_fn); - - internal async Task SendCommandAndCheck (JObject args, string method, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action locals_fn = null, string waitForEvent = Inspector.PAUSE) - { - var res = await ctx.cli.SendCommand (method, args, ctx.token); - if (!res.IsOk) { - Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}"); - Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}"); - } - - var wait_res = await ctx.insp.WaitFor(waitForEvent); - - if (function_name != null) - Assert.Equal (function_name, wait_res ["callFrames"]?[0]?["functionName"]?.Value ()); - - if (script_loc != null) - CheckLocation (script_loc, line, column, ctx.scripts, wait_res ["callFrames"][0]["location"]); - - if (wait_for_event_fn != null) - await wait_for_event_fn (wait_res); - - if (locals_fn != null) { - var locals = await GetProperties (wait_res ["callFrames"][0]["callFrameId"].Value ()); - locals_fn (locals); - } - - return wait_res; - } - - internal async Task CheckDelegate (JToken locals, string name, string className, string target) - { - var l = GetAndAssertObjectWithName (locals, name); - var val = l["value"]; - - await CheckDelegate (l, TDelegate (className, target), name); - } - - internal async Task CheckDelegate (JToken actual_val, JToken exp_val, string label) - { - AssertEqual ("object", actual_val["type"]?.Value(), $"{label}-type"); - AssertEqual (exp_val ["className"]?.Value (), actual_val ["className"]?.Value(), $"{label}-className"); - - var actual_target = actual_val["description"]?.Value(); - Assert.True(actual_target != null, $"${label}-description"); - var exp_target = exp_val["target"].Value(); - - CheckDelegateTarget (actual_target, exp_target); - - var del_props = await GetProperties(actual_val["objectId"]?.Value()); - AssertEqual (1, del_props.Count(), $"${label}-delegate-properties-count"); - - var obj = del_props.Where (jt => jt ["name"]?.Value () == "Target").FirstOrDefault (); - Assert.True (obj != null, $"[{label}] Property named 'Target' found found in delegate properties"); - - AssertEqual("symbol", obj ["value"]?["type"]?.Value(), $"{label}#Target#type"); - CheckDelegateTarget(obj ["value"]?["value"]?.Value(), exp_target); - - return; - - void CheckDelegateTarget(string actual_target, string exp_target) - { - var parts = exp_target.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 1) { - // not a generated method - AssertEqual(exp_target, actual_target, $"{label}-description"); - } else { - bool prefix = actual_target.StartsWith(parts[0], StringComparison.Ordinal); - Assert.True(prefix, $"{label}-description, Expected target to start with '{parts[0]}'. Actual: '{actual_target}'"); - - var remaining = actual_target.Substring(parts[0].Length); - bool suffix = remaining.EndsWith(parts[1], StringComparison.Ordinal); - Assert.True(prefix, $"{label}-description, Expected target to end with '{parts[1]}'. Actual: '{remaining}'"); - } - } - } - - internal async Task CheckCustomType (JToken actual_val, JToken exp_val, string label) - { - var ctype = exp_val["__custom_type"].Value(); - switch (ctype) { - case "delegate": - await CheckDelegate (actual_val, exp_val, label); - break; - - case "pointer": { - - if (exp_val ["is_null"]?.Value() == true) { - AssertEqual ("symbol", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_val_str = $"({exp_val ["type_name"]?.Value()}) 0"; - AssertEqual (exp_val_str, actual_val ["value"]?.Value (), $"{label}-value"); - AssertEqual (exp_val_str, actual_val ["description"]?.Value (), $"{label}-description"); - } else if (exp_val ["is_void"]?.Value () == true) { - AssertEqual ("symbol", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_val_str = $"({exp_val ["type_name"]?.Value()})"; - AssertStartsWith (exp_val_str, actual_val ["value"]?.Value (), $"{label}-value"); - AssertStartsWith (exp_val_str, actual_val ["description"]?.Value (), $"{label}-description"); - } else { - AssertEqual ("object", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_prefix = $"({exp_val ["type_name"]?.Value()})"; - AssertStartsWith (exp_prefix, actual_val ["className"]?.Value (), $"{label}-className"); - AssertStartsWith (exp_prefix, actual_val ["description"]?.Value (), $"{label}-description"); - Assert.False (actual_val ["className"]?.Value () == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}"); - } - break; - } - - case "getter": { - // For getter, `actual_val` is not `.value`, instead it's the container object - // which has a `.get` instead of a `.value` - var get = actual_val ["get"]; - Assert.True (get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']": String.Empty)}"); - - AssertEqual ("Function", get ["className"]?.Value (), $"{label}-className"); - AssertStartsWith ($"get {exp_val ["type_name"]?.Value ()} ()", get ["description"]?.Value (), $"{label}-description"); - AssertEqual ("function", get ["type"]?.Value (), $"{label}-type"); - - break; - } - - case "ignore_me": - // nothing to check ;) - break; - - default: - throw new ArgumentException($"{ctype} not supported"); - } - } - - internal async Task CheckProps (JToken actual, object exp_o, string label, int num_fields=-1) - { - if (exp_o.GetType ().IsArray || exp_o is JArray) { - if (! (actual is JArray actual_arr)) { - Assert.True (false, $"[{label}] Expected to get an array here but got {actual}"); - return; - } - - var exp_v_arr = JArray.FromObject (exp_o); - AssertEqual (exp_v_arr.Count, actual_arr.Count (), $"{label}-count"); - - for (int i = 0; i < exp_v_arr.Count; i ++) { - var exp_i = exp_v_arr [i]; - var act_i = actual_arr [i]; - - AssertEqual (i.ToString (), act_i ["name"]?.Value (), $"{label}-[{i}].name"); - if (exp_i != null) - await CheckValue (act_i["value"], exp_i, $"{label}-{i}th value"); - } - - return; - } - - // Not an array - var exp = exp_o as JObject; - if (exp == null) - exp = JObject.FromObject(exp_o); - - num_fields = num_fields < 0 ? exp.Values().Count() : num_fields; - Assert.True(num_fields == actual.Count(), $"[{label}] Number of fields don't match, Expected: {num_fields}, Actual: {actual.Count()}"); - - foreach (var kvp in exp) { - var exp_name = kvp.Key; - var exp_val = kvp.Value; - - var actual_obj = actual.FirstOrDefault(jt => jt["name"]?.Value() == exp_name); - if (actual_obj == null) { - Assert.True(actual_obj != null, $"[{label}] Could not find property named '{exp_name}'"); - } - - Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'"); - - var actual_val = actual_obj["value"]; - if (exp_val.Type == JTokenType.Array) { - var actual_props = await GetProperties(actual_val["objectId"]?.Value()); - await CheckProps (actual_props, exp_val, $"{label}-{exp_name}"); - } else if (exp_val ["__custom_type"] != null && exp_val ["__custom_type"]?.Value () == "getter") { - // hack: for getters, actual won't have a .value - await CheckCustomType (actual_obj, exp_val, $"{label}#{exp_name}"); - } else { - await CheckValue (actual_val, exp_val, $"{label}#{exp_name}"); - } - } - } - - internal async Task CheckValue (JToken actual_val, JToken exp_val, string label) - { - if (exp_val ["__custom_type"] != null) { - await CheckCustomType (actual_val, exp_val, label); - return; - } - - if (exp_val ["type"] == null && actual_val ["objectId"] != null) { - var new_val = await GetProperties (actual_val ["objectId"].Value ()); - await CheckProps (new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value()}"); - return; - } - - foreach (var jp in exp_val.Values ()) { - if (jp.Value.Type == JTokenType.Object) { - var new_val = await GetProperties (actual_val ["objectId"].Value ()); - await CheckProps (new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value()}"); - - continue; - } - - var exp_val_str = jp.Value.Value (); - bool null_or_empty_exp_val = String.IsNullOrEmpty (exp_val_str); - - var actual_field_val = actual_val.Values ().FirstOrDefault (a_jp => a_jp.Name == jp.Name); - var actual_field_val_str = actual_field_val?.Value?.Value (); - if (null_or_empty_exp_val && String.IsNullOrEmpty (actual_field_val_str)) - continue; - - Assert.True (actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}"); - - Assert.True (exp_val_str == actual_field_val_str, - $"[{label}] Value for json property named {jp.Name} didn't match.\n" + - $"Expected: {jp.Value.Value ()}\n" + - $"Actual: {actual_field_val.Value.Value ()}"); - } - } - - internal async Task GetLocalsForFrame (JToken frame, string script_loc, int line, int column, string function_name) - { - CheckLocation (script_loc, line, column, ctx.scripts, frame ["location"]); - Assert.Equal (function_name, frame ["functionName"].Value ()); - - return await GetProperties (frame ["callFrameId"].Value ()); - } - - internal async Task GetObjectOnFrame (JToken frame, string name) - { - var locals = await GetProperties (frame ["callFrameId"].Value ()); - return await GetObjectOnLocals (locals, name); - } - - // Find an object with @name, *fetch* the object, and check against @o - internal async Task CompareObjectPropertiesFor (JToken locals, string name, object o, string label = null, int num_fields = -1) - { - if (label == null) - label = name; - var props = await GetObjectOnLocals (locals, name); - try { - if (o != null) - await CheckProps (props, o, label, num_fields); - return props; - } catch { - throw; - } - } - - internal async Task GetObjectOnLocals (JToken locals, string name) - { - var obj = GetAndAssertObjectWithName (locals, name); - var objectId = obj ["value"]["objectId"]?.Value (); - Assert.True (!String.IsNullOrEmpty (objectId), $"No objectId found for {name}"); - - return await GetProperties (objectId); - } - - /* @fn_args is for use with `Runtime.callFunctionOn` only */ - internal async Task GetProperties (string id, JToken fn_args = null) - { - if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith ("dotnet:scope:")) { - var fn_decl = "function () { return this; }"; - var cfo_args = JObject.FromObject (new { - functionDeclaration = fn_decl, - objectId = id - }); - if (fn_args != null) - cfo_args ["arguments"] = fn_args; - - var result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token); - AssertEqual (true, result.IsOk, $"Runtime.getProperties failed for {cfo_args.ToString ()}, with Result: {result}"); - id = result.Value ["result"]?["objectId"]?.Value (); - } - - var get_prop_req = JObject.FromObject (new { - objectId = id - }); - - var frame_props = await ctx.cli.SendCommand ("Runtime.getProperties", get_prop_req, ctx.token); - if (!frame_props.IsOk) - Assert.True (false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {frame_props}"); - - var locals = frame_props.Value ["result"]; - // FIXME: Should be done when generating the list in library_mono.js, but not sure yet - // whether to remove it, and how to do it correctly. - if (locals is JArray) { - foreach (var p in locals) { - if (p ["name"]?.Value () == "length" && p ["enumerable"]?.Value () != true) { - p.Remove (); - break; - } - } - } - - return locals; - } - - internal async Task EvaluateOnCallFrame (string id, string expression) - { - var evaluate_req = JObject.FromObject (new { - callFrameId = id, - expression = expression - }); - - var frame_evaluate = await ctx.cli.SendCommand ("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token); - if (!frame_evaluate.IsOk) - Assert.True (false, $"Debugger.evaluateOnCallFrame failed for {evaluate_req.ToString ()}, with Result: {frame_evaluate}"); - - var evaluate_result = frame_evaluate.Value ["result"]; - return evaluate_result; - } - - internal async Task SetBreakpoint (string url_key, int line, int column, bool expect_ok=true, bool use_regex = false) - { - var bp1_req = !use_regex ? - JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key],}) : - JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, }); - - var bp1_res = await ctx.cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, ctx.token); - Assert.True (expect_ok ? bp1_res.IsOk : bp1_res.IsErr); - - return bp1_res; - } - - internal async Task SetBreakpointInMethod (string assembly, string type, string method, int lineOffset = 0, int col = 0) { - var req = JObject.FromObject (new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset }); - - // Protocol extension - var res = await ctx.cli.SendCommand ("DotnetDebugger.getMethodLocation", req, ctx.token); - Assert.True (res.IsOk); - - var m_url = res.Value ["result"]["url"].Value (); - var m_line = res.Value ["result"]["line"].Value (); - - var bp1_req = JObject.FromObject(new { - lineNumber = m_line + lineOffset, - columnNumber = col, - url = m_url - }); - - res = await ctx.cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, ctx.token); - Assert.True (res.IsOk); - - return res; - } - - internal void AssertEqual (object expected, object actual, string label) - => Assert.True (expected?.Equals (actual), - $"[{label}]\n" + - $"Expected: {expected?.ToString()}\n" + - $"Actual: {actual?.ToString()}\n"); - - internal void AssertStartsWith (string expected, string actual, string label) - => Assert.True(actual?.StartsWith (expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}"); - - internal static Func TSimpleClass = (X, Y, Id, Color) => new { - X = TNumber (X), - Y = TNumber (Y), - Id = TString (Id), - Color = TEnum ("DebuggerTests.RGB", Color), - PointWithCustomGetter = TGetter ("PointWithCustomGetter") - }; - - internal static Func TPoint = (X, Y, Id, Color) => new { - X = TNumber (X), - Y = TNumber (Y), - Id = TString (Id), - Color = TEnum ("DebuggerTests.RGB", Color), - }; - - //FIXME: um maybe we don't need to convert jobject right here! - internal static JObject TString (string value) => - value == null - ? TObject ("string", is_null: true) - : JObject.FromObject (new { type = "string", value = @value, description = @value }); - - internal static JObject TNumber (int value) => - JObject.FromObject (new { type = "number", value = @value.ToString (), description = value.ToString () }); - - internal static JObject TValueType (string className, string description = null, object members = null) => - JObject.FromObject (new { type = "object", isValueType = true, className = className, description = description ?? className }); - - internal static JObject TEnum (string className, string descr, object members = null) => - JObject.FromObject (new { type = "object", isEnum = true, className = className, description = descr }); - - internal static JObject TObject (string className, string description = null, bool is_null = false) => - is_null - ? JObject.FromObject (new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null }) - : JObject.FromObject (new { type = "object", className = className, description = description ?? className }); - - internal static JObject TArray (string className, int length = 0) - => JObject.FromObject (new { type = "object", className = className, description = $"{className}({length})", subtype = "array" }); - - internal static JObject TBool (bool value) - => JObject.FromObject (new { type = "boolean", value = @value, description = @value ? "true" : "false" }); - - internal static JObject TSymbol (string value) - => JObject.FromObject (new { type = "symbol", value = @value, description = @value }); - - /* - For target names with generated method names like - `void b__11_0 (Math.GenericStruct)` - - .. pass target "as `target: "void |(Math.GenericStruct)"` - */ - internal static JObject TDelegate(string className, string target) - => JObject.FromObject(new { - __custom_type = "delegate", - className = className, - target = target - }); - - internal static JObject TPointer (string type_name, bool is_null = false) - => JObject.FromObject (new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith ("void*") }); - - internal static JObject TIgnore () - => JObject.FromObject (new { __custom_type = "ignore_me" }); - - internal static JObject TGetter (string type) - => JObject.FromObject (new { __custom_type = "getter", type_name = type }); - } - - class DebugTestContext - { - public InspectorClient cli; - public Inspector insp; - public CancellationToken token; - public Dictionary scripts; - - public bool UseCallFunctionOnBeforeGetProperties; - - public DebugTestContext (InspectorClient cli, Inspector insp, CancellationToken token, Dictionary scripts) - { - this.cli = cli; - this.insp = insp; - this.token = token; - this.scripts = scripts; - } - } - - enum StepKind - { - Into, - Over, - Out - } -} + class Inspector + { + // InspectorClient client; + Dictionary> notifications = new Dictionary>(); + Dictionary> eventListeners = new Dictionary>(); + + public const string PAUSE = "pause"; + public const string READY = "ready"; + + public Task WaitFor(string what) + { + if (notifications.ContainsKey(what)) + throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup"); + var n = new TaskCompletionSource(); + notifications[what] = n; + return n.Task; + } + + void NotifyOf(string what, JObject args) + { + if (!notifications.ContainsKey(what)) + throw new Exception($"Invalid internal state, notifying of {what}, but nobody waiting"); + notifications[what].SetResult(args); + notifications.Remove(what); + } + + public void On(string evtName, Func cb) + { + eventListeners[evtName] = cb; + } + + void FailAllWaitersWithException(JObject exception) + { + foreach (var tcs in notifications.Values) + tcs.SetException(new ArgumentException(exception.ToString())); + } + + async Task OnMessage(string method, JObject args, CancellationToken token) + { + //System.Console.WriteLine("OnMessage " + method + args); + switch (method) + { + case "Debugger.paused": + NotifyOf(PAUSE, args); + break; + case "Mono.runtimeReady": + NotifyOf(READY, args); + break; + case "Runtime.consoleAPICalled": + Console.WriteLine("CWL: {0}", args?["args"]?[0]?["value"]); + break; + } + if (eventListeners.ContainsKey(method)) + await eventListeners[method](args, token); + else if (String.Compare(method, "Runtime.exceptionThrown") == 0) + FailAllWaitersWithException(args); + } + + public async Task Ready(Func cb = null, TimeSpan? span = null) + { + using (var cts = new CancellationTokenSource()) + { + cts.CancelAfter(span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default + var uri = new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect"); + using var loggerFactory = LoggerFactory.Create( + builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); + using (var client = new InspectorClient(loggerFactory.CreateLogger())) + { + await client.Connect(uri, OnMessage, async token => + { + Task[] init_cmds = { + client.SendCommand("Profiler.enable", null, token), + client.SendCommand("Runtime.enable", null, token), + client.SendCommand("Debugger.enable", null, token), + client.SendCommand("Runtime.runIfWaitingForDebugger", null, token), + WaitFor(READY), + }; + // await Task.WhenAll (init_cmds); + Console.WriteLine("waiting for the runtime to be ready"); + await init_cmds[4]; + Console.WriteLine("runtime ready, TEST TIME"); + if (cb != null) + { + Console.WriteLine("await cb(client, token)"); + await cb(client, token); + } + + }, cts.Token); + await client.Close(cts.Token); + } + } + } + } + + public class DebuggerTestBase + { + protected Task startTask; + + static string FindTestPath() + { + //FIXME how would I locate it otherwise? + var test_path = Environment.GetEnvironmentVariable("TEST_SUITE_PATH"); + //Lets try to guest + if (test_path != null && Directory.Exists(test_path)) + return test_path; + + var cwd = Environment.CurrentDirectory; + Console.WriteLine("guessing from {0}", cwd); + //tests run from DebuggerTestSuite/bin/Debug/netcoreapp2.1 + var new_path = Path.Combine(cwd, "../../../../bin/debugger-test-suite"); + if (File.Exists(Path.Combine(new_path, "debugger-driver.html"))) + return new_path; + + throw new Exception("Missing TEST_SUITE_PATH env var and could not guess path from CWD"); + } + + static string[] PROBE_LIST = { + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + }; + static string chrome_path; + + static string FindChromePath() + { + if (chrome_path != null) + return chrome_path; + + foreach (var s in PROBE_LIST) + { + if (File.Exists(s)) + { + chrome_path = s; + Console.WriteLine($"Using chrome path: ${s}"); + return s; + } + } + throw new Exception("Could not find an installed Chrome to use"); + } + + public DebuggerTestBase(string driver = "debugger-driver.html") + { + startTask = TestHarnessProxy.Start(FindChromePath(), FindTestPath(), driver); + } + + public Task Ready() => startTask; + + internal DebugTestContext ctx; + internal Dictionary dicScriptsIdToUrl; + internal Dictionary dicFileToUrl; + internal Dictionary SubscribeToScripts(Inspector insp) + { + dicScriptsIdToUrl = new Dictionary(); + dicFileToUrl = new Dictionary(); + insp.On("Debugger.scriptParsed", async (args, c) => + { + var script_id = args?["scriptId"]?.Value(); + var url = args["url"]?.Value(); + if (script_id.StartsWith("dotnet://")) + { + var dbgUrl = args["dotNetUrl"]?.Value(); + var arrStr = dbgUrl.Split("/"); + dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; + dicScriptsIdToUrl[script_id] = dbgUrl; + dicFileToUrl[dbgUrl] = args["url"]?.Value(); + } + else if (!String.IsNullOrEmpty(url)) + { + dicFileToUrl[new Uri(url).AbsolutePath] = url; + } + await Task.FromResult(0); + }); + return dicScriptsIdToUrl; + } + + internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression, + Action test_fn = null, Func wait_for_event_fn = null, bool use_cfo = false) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + var bp = await SetBreakpoint(url_key, line, column); + + await EvaluateAndCheck( + eval_expression, url_key, line, column, + function_name, + wait_for_event_fn: async (pause_location) => + { + //make sure we're on the right bp + + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + + var scope = top_frame["scopeChain"][0]; + Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]); + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); + else + await Task.CompletedTask; + }, + locals_fn: (locals) => + { + if (test_fn != null) + test_fn(locals); + } + ); + }); + } + + // sets breakpoint by method name and line offset + internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression, + Action locals_fn = null, Func wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col); + + var args = JObject.FromObject(new { expression = eval_expression }); + var res = await ctx.cli.SendCommand("Runtime.evaluate", args, ctx.token); + if (!res.IsOk) + { + Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); + Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); + } + + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + if (bp_function_name != null) + Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value()); + + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + + var scope = top_frame["scopeChain"][0]; + Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]); + + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); + + if (locals_fn != null) + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + locals_fn(locals); + } + }); + } + + internal void CheckLocation(string script_loc, int line, int column, Dictionary scripts, JToken location) + { + var loc_str = $"{ scripts[location["scriptId"].Value()] }" + + $"#{ location["lineNumber"].Value() }" + + $"#{ location["columnNumber"].Value() }"; + + var expected_loc_str = $"{script_loc}#{line}#{column}"; + Assert.Equal(expected_loc_str, loc_str); + } + + internal void CheckNumber(JToken locals, string name, T value) + { + foreach (var l in locals) + { + if (name != l["name"]?.Value()) + continue; + var val = l["value"]; + Assert.Equal("number", val["type"]?.Value()); + Assert.Equal(value, val["value"].Value()); + Assert.Equal(value.ToString(), val["description"].Value().ToString()); + return; + } + Assert.True(false, $"Could not find variable '{name}'"); + } + + internal void CheckString(JToken locals, string name, string value) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], TString(value), name).Wait(); + } + + internal JToken CheckSymbol(JToken locals, string name, string value) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], TSymbol(value), name).Wait(); + return l; + } + + internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false) + { + var l = GetAndAssertObjectWithName(locals, name); + var val = l["value"]; + CheckValue(val, TObject(class_name, is_null: is_null), name).Wait(); + Assert.True(val["isValueType"] == null || !val["isValueType"].Value()); + + return l; + } + + internal async Task CheckPointerValue(JToken locals, string name, JToken expected, string label = null) + { + var l = GetAndAssertObjectWithName(locals, name); + await CheckValue(l["value"], expected, $"{label ?? String.Empty}-{name}"); + return l; + } + + internal async Task CheckDateTime(JToken value, DateTime expected, string label = "") + { + await CheckValue(value, TValueType("System.DateTime", expected.ToString()), label); + await CheckDateTimeValue(value, expected); + } + + internal async Task CheckDateTime(JToken locals, string name, DateTime expected) + { + var obj = GetAndAssertObjectWithName(locals, name); + await CheckDateTimeValue(obj["value"], expected); + } + + internal async Task CheckDateTimeValue(JToken value, DateTime expected) + { + await CheckDateTimeMembers(value, expected); + + var res = await InvokeGetter(JObject.FromObject(new { value = value }), "Date"); + await CheckDateTimeMembers(res.Value["result"], expected.Date); + + // FIXME: check some float properties too + + async Task CheckDateTimeMembers(JToken v, DateTime exp_dt) + { + AssertEqual("System.DateTime", v["className"]?.Value(), "className"); + AssertEqual(exp_dt.ToString(), v["description"]?.Value(), "description"); + + var members = await GetProperties(v["objectId"]?.Value()); + + // not checking everything + CheckNumber(members, "Year", exp_dt.Year); + CheckNumber(members, "Month", exp_dt.Month); + CheckNumber(members, "Day", exp_dt.Day); + CheckNumber(members, "Hour", exp_dt.Hour); + CheckNumber(members, "Minute", exp_dt.Minute); + CheckNumber(members, "Second", exp_dt.Second); + } + } + + internal JToken CheckBool(JToken locals, string name, bool expected) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], TBool(expected), name).Wait(); + return l; + } + + internal void CheckContentValue(JToken token, string value) + { + var val = token["value"].Value(); + Assert.Equal(value, val); + } + + internal JToken CheckValueType(JToken locals, string name, string class_name) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], TValueType(class_name), name).Wait(); + return l; + } + + internal JToken CheckEnum(JToken locals, string name, string class_name, string descr) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], TEnum(class_name, descr), name).Wait(); + return l; + } + + internal void CheckArray(JToken locals, string name, string class_name, int length) + => CheckValue( + GetAndAssertObjectWithName(locals, name)["value"], + TArray(class_name, length), name).Wait(); + + internal JToken GetAndAssertObjectWithName(JToken obj, string name) + { + var l = obj.FirstOrDefault(jt => jt["name"]?.Value() == name); + if (l == null) + Assert.True(false, $"Could not find variable '{name}'"); + return l; + } + + internal async Task SendCommand(string method, JObject args) + { + var res = await ctx.cli.SendCommand(method, args, ctx.token); + if (!res.IsOk) + { + Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); + Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); + } + return res; + } + + internal async Task Evaluate(string expression) + { + return await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expression })); + } + + internal void AssertLocation(JObject args, string methodName) + { + Assert.Equal(methodName, args["callFrames"]?[0]?["functionName"]?.Value()); + } + + // Place a breakpoint in the given method and run until its hit + // Return the Debugger.paused data + internal async Task RunUntil(string methodName) + { + await SetBreakpointInMethod("debugger-test", "DebuggerTest", methodName); + // This will run all the tests until it hits the bp + await Evaluate("window.setTimeout(function() { invoke_run_all (); }, 1);"); + var wait_res = await ctx.insp.WaitFor(Inspector.PAUSE); + AssertLocation(wait_res, "locals_inner"); + return wait_res; + } + + internal async Task InvokeGetter(JToken obj, object arguments, string fn = "function(e){return this[e]}", bool expect_ok = true, bool? returnByValue = null) + { + var req = JObject.FromObject(new + { + functionDeclaration = fn, + objectId = obj["value"]?["objectId"]?.Value(), + arguments = new[] { new { value = arguments } } + }); + if (returnByValue != null) + req["returnByValue"] = returnByValue.Value; + + var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", req, ctx.token); + Assert.True(expect_ok == res.IsOk, $"InvokeGetter failed for {req} with {res}"); + + return res; + } + + internal async Task StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action locals_fn = null, int times = 1) + { + for (int i = 0; i < times - 1; i++) + { + await SendCommandAndCheck(null, $"Debugger.step{kind.ToString()}", null, -1, -1, null); + } + + // Check for method/line etc only at the last step + return await SendCommandAndCheck( + null, $"Debugger.step{kind.ToString()}", script_loc, line, column, function_name, + wait_for_event_fn: wait_for_event_fn, + locals_fn: locals_fn); + } + + internal async Task EvaluateAndCheck(string expression, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action locals_fn = null) => await SendCommandAndCheck( + JObject.FromObject(new { expression = expression }), + "Runtime.evaluate", script_loc, line, column, function_name, + wait_for_event_fn: wait_for_event_fn, + locals_fn: locals_fn); + + internal async Task SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action locals_fn = null, string waitForEvent = Inspector.PAUSE) + { + var res = await ctx.cli.SendCommand(method, args, ctx.token); + if (!res.IsOk) + { + Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); + Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); + } + + var wait_res = await ctx.insp.WaitFor(waitForEvent); + + if (function_name != null) + Assert.Equal(function_name, wait_res["callFrames"]?[0]?["functionName"]?.Value()); + + if (script_loc != null) + CheckLocation(script_loc, line, column, ctx.scripts, wait_res["callFrames"][0]["location"]); + + if (wait_for_event_fn != null) + await wait_for_event_fn(wait_res); + + if (locals_fn != null) + { + var locals = await GetProperties(wait_res["callFrames"][0]["callFrameId"].Value()); + locals_fn(locals); + } + + return wait_res; + } + + internal async Task CheckDelegate(JToken locals, string name, string className, string target) + { + var l = GetAndAssertObjectWithName(locals, name); + var val = l["value"]; + + await CheckDelegate(l, TDelegate(className, target), name); + } + + internal async Task CheckDelegate(JToken actual_val, JToken exp_val, string label) + { + AssertEqual("object", actual_val["type"]?.Value(), $"{label}-type"); + AssertEqual(exp_val["className"]?.Value(), actual_val["className"]?.Value(), $"{label}-className"); + + var actual_target = actual_val["description"]?.Value(); + Assert.True(actual_target != null, $"${label}-description"); + var exp_target = exp_val["target"].Value(); + + CheckDelegateTarget(actual_target, exp_target); + + var del_props = await GetProperties(actual_val["objectId"]?.Value()); + AssertEqual(1, del_props.Count(), $"${label}-delegate-properties-count"); + + var obj = del_props.Where(jt => jt["name"]?.Value() == "Target").FirstOrDefault(); + Assert.True(obj != null, $"[{label}] Property named 'Target' found found in delegate properties"); + + AssertEqual("symbol", obj["value"]?["type"]?.Value(), $"{label}#Target#type"); + CheckDelegateTarget(obj["value"]?["value"]?.Value(), exp_target); + + return; + + void CheckDelegateTarget(string actual_target, string exp_target) + { + var parts = exp_target.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 1) + { + // not a generated method + AssertEqual(exp_target, actual_target, $"{label}-description"); + } + else + { + bool prefix = actual_target.StartsWith(parts[0], StringComparison.Ordinal); + Assert.True(prefix, $"{label}-description, Expected target to start with '{parts[0]}'. Actual: '{actual_target}'"); + + var remaining = actual_target.Substring(parts[0].Length); + bool suffix = remaining.EndsWith(parts[1], StringComparison.Ordinal); + Assert.True(prefix, $"{label}-description, Expected target to end with '{parts[1]}'. Actual: '{remaining}'"); + } + } + } + + internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string label) + { + var ctype = exp_val["__custom_type"].Value(); + switch (ctype) + { + case "delegate": + await CheckDelegate(actual_val, exp_val, label); + break; + + case "pointer": + { + + if (exp_val["is_null"]?.Value() == true) + { + AssertEqual("symbol", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_val_str = $"({exp_val["type_name"]?.Value()}) 0"; + AssertEqual(exp_val_str, actual_val["value"]?.Value(), $"{label}-value"); + AssertEqual(exp_val_str, actual_val["description"]?.Value(), $"{label}-description"); + } + else if (exp_val["is_void"]?.Value() == true) + { + AssertEqual("symbol", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_val_str = $"({exp_val["type_name"]?.Value()})"; + AssertStartsWith(exp_val_str, actual_val["value"]?.Value(), $"{label}-value"); + AssertStartsWith(exp_val_str, actual_val["description"]?.Value(), $"{label}-description"); + } + else + { + AssertEqual("object", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_prefix = $"({exp_val["type_name"]?.Value()})"; + AssertStartsWith(exp_prefix, actual_val["className"]?.Value(), $"{label}-className"); + AssertStartsWith(exp_prefix, actual_val["description"]?.Value(), $"{label}-description"); + Assert.False(actual_val["className"]?.Value() == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}"); + } + break; + } + + case "getter": + { + // For getter, `actual_val` is not `.value`, instead it's the container object + // which has a `.get` instead of a `.value` + var get = actual_val["get"]; + Assert.True(get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']" : String.Empty)}"); + + AssertEqual("Function", get["className"]?.Value(), $"{label}-className"); + AssertStartsWith($"get {exp_val["type_name"]?.Value()} ()", get["description"]?.Value(), $"{label}-description"); + AssertEqual("function", get["type"]?.Value(), $"{label}-type"); + + break; + } + + case "datetime": + { + var dateTime = DateTime.FromBinary(exp_val["binary"].Value()); + await CheckDateTime(actual_val, dateTime, label); + break; + } + + case "ignore_me": + // nothing to check ;) + break; + + default: + throw new ArgumentException($"{ctype} not supported"); + } + } + + internal async Task CheckProps(JToken actual, object exp_o, string label, int num_fields = -1) + { + if (exp_o.GetType().IsArray || exp_o is JArray) + { + if (!(actual is JArray actual_arr)) + { + Assert.True(false, $"[{label}] Expected to get an array here but got {actual}"); + return; + } + + var exp_v_arr = JArray.FromObject(exp_o); + AssertEqual(exp_v_arr.Count, actual_arr.Count(), $"{label}-count"); + + for (int i = 0; i < exp_v_arr.Count; i++) + { + var exp_i = exp_v_arr[i]; + var act_i = actual_arr[i]; + + AssertEqual(i.ToString(), act_i["name"]?.Value(), $"{label}-[{i}].name"); + if (exp_i != null) + await CheckValue(act_i["value"], exp_i, $"{label}-{i}th value"); + } + + return; + } + + // Not an array + var exp = exp_o as JObject; + if (exp == null) + exp = JObject.FromObject(exp_o); + + num_fields = num_fields < 0 ? exp.Values().Count() : num_fields; + Assert.True(num_fields == actual.Count(), $"[{label}] Number of fields don't match, Expected: {num_fields}, Actual: {actual.Count()}"); + + foreach (var kvp in exp) + { + var exp_name = kvp.Key; + var exp_val = kvp.Value; + + var actual_obj = actual.FirstOrDefault(jt => jt["name"]?.Value() == exp_name); + if (actual_obj == null) + { + Assert.True(actual_obj != null, $"[{label}] Could not find property named '{exp_name}'"); + } + + Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'"); + + var actual_val = actual_obj["value"]; + if (exp_val.Type == JTokenType.Array) + { + var actual_props = await GetProperties(actual_val["objectId"]?.Value()); + await CheckProps(actual_props, exp_val, $"{label}-{exp_name}"); + } + else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value() == "getter") + { + // hack: for getters, actual won't have a .value + await CheckCustomType(actual_obj, exp_val, $"{label}#{exp_name}"); + } + else + { + await CheckValue(actual_val, exp_val, $"{label}#{exp_name}"); + } + } + } + + internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) + { + if (exp_val["__custom_type"] != null) + { + await CheckCustomType(actual_val, exp_val, label); + return; + } + + if (exp_val["type"] == null && actual_val["objectId"] != null) + { + var new_val = await GetProperties(actual_val["objectId"].Value()); + await CheckProps(new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value()}"); + return; + } + + foreach (var jp in exp_val.Values()) + { + if (jp.Value.Type == JTokenType.Object) + { + var new_val = await GetProperties(actual_val["objectId"].Value()); + await CheckProps(new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value()}"); + + continue; + } + + var exp_val_str = jp.Value.Value(); + bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str); + + var actual_field_val = actual_val.Values().FirstOrDefault(a_jp => a_jp.Name == jp.Name); + var actual_field_val_str = actual_field_val?.Value?.Value(); + if (null_or_empty_exp_val && String.IsNullOrEmpty(actual_field_val_str)) + continue; + + Assert.True(actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}"); + + Assert.True(exp_val_str == actual_field_val_str, + $"[{label}] Value for json property named {jp.Name} didn't match.\n" + + $"Expected: {jp.Value.Value()}\n" + + $"Actual: {actual_field_val.Value.Value()}"); + } + } + + internal async Task GetLocalsForFrame(JToken frame, string script_loc, int line, int column, string function_name) + { + CheckLocation(script_loc, line, column, ctx.scripts, frame["location"]); + Assert.Equal(function_name, frame["functionName"].Value()); + + return await GetProperties(frame["callFrameId"].Value()); + } + + internal async Task GetObjectOnFrame(JToken frame, string name) + { + var locals = await GetProperties(frame["callFrameId"].Value()); + return await GetObjectOnLocals(locals, name); + } + + // Find an object with @name, *fetch* the object, and check against @o + internal async Task CompareObjectPropertiesFor(JToken locals, string name, object o, string label = null, int num_fields = -1) + { + if (label == null) + label = name; + var props = await GetObjectOnLocals(locals, name); + try + { + if (o != null) + await CheckProps(props, o, label, num_fields); + return props; + } + catch + { + throw; + } + } + + internal async Task GetObjectOnLocals(JToken locals, string name) + { + var obj = GetAndAssertObjectWithName(locals, name); + var objectId = obj["value"]["objectId"]?.Value(); + Assert.True(!String.IsNullOrEmpty(objectId), $"No objectId found for {name}"); + + return await GetProperties(objectId); + } + + /* @fn_args is for use with `Runtime.callFunctionOn` only */ + internal async Task GetProperties(string id, JToken fn_args = null, bool expect_ok = true) + { + if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) + { + var fn_decl = "function () { return this; }"; + var cfo_args = JObject.FromObject(new + { + functionDeclaration = fn_decl, + objectId = id + }); + if (fn_args != null) + cfo_args["arguments"] = fn_args; + + var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}"); + if (!result.IsOk) + return null; + id = result.Value["result"]?["objectId"]?.Value(); + } + + var get_prop_req = JObject.FromObject(new + { + objectId = id + }); + + var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); + if (!frame_props.IsOk) + return null; + + var locals = frame_props.Value["result"]; + // FIXME: Should be done when generating the list in library_mono.js, but not sure yet + // whether to remove it, and how to do it correctly. + if (locals is JArray) + { + foreach (var p in locals) + { + if (p["name"]?.Value() == "length" && p["enumerable"]?.Value() != true) + { + p.Remove(); + break; + } + } + } + + return locals; + } + + internal async Task<(JToken, Result)> EvaluateOnCallFrame(string id, string expression, bool expect_ok = true) + { + var evaluate_req = JObject.FromObject(new + { + callFrameId = id, + expression = expression + }); + + var res = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token); + AssertEqual(expect_ok, res.IsOk, $"Debugger.evaluateOnCallFrame ('{expression}', scope: {id}) returned {res.IsOk} instead of {expect_ok}, with Result: {res}"); + if (res.IsOk) + return (res.Value["result"], res); + + return (null, res); + } + + internal async Task SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false) + { + var bp1_req = !use_regex ? + JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], }) : + JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, }); + + var bp1_res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr); + + return bp1_res; + } + + internal async Task SetPauseOnException(string state) + { + var exc_res = await ctx.cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), ctx.token); + return exc_res; + } + + internal async Task SetBreakpointInMethod(string assembly, string type, string method, int lineOffset = 0, int col = 0) + { + var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset }); + + // Protocol extension + var res = await ctx.cli.SendCommand("DotnetDebugger.getMethodLocation", req, ctx.token); + Assert.True(res.IsOk); + + var m_url = res.Value["result"]["url"].Value(); + var m_line = res.Value["result"]["line"].Value(); + + var bp1_req = JObject.FromObject(new + { + lineNumber = m_line + lineOffset, + columnNumber = col, + url = m_url + }); + + res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + Assert.True(res.IsOk); + + return res; + } + + internal void AssertEqual(object expected, object actual, string label) + { + if (expected?.Equals(actual) == true) + return; + + throw new AssertActualExpectedException( + expected, actual, + $"[{label}]\n"); + } + + internal void AssertStartsWith(string expected, string actual, string label) => Assert.True(actual?.StartsWith(expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}"); + + internal static Func TSimpleClass = (X, Y, Id, Color) => new + { + X = TNumber(X), + Y = TNumber(Y), + Id = TString(Id), + Color = TEnum("DebuggerTests.RGB", Color), + PointWithCustomGetter = TGetter("PointWithCustomGetter") + }; + + internal static Func TPoint = (X, Y, Id, Color) => new + { + X = TNumber(X), + Y = TNumber(Y), + Id = TString(Id), + Color = TEnum("DebuggerTests.RGB", Color), + }; + + //FIXME: um maybe we don't need to convert jobject right here! + internal static JObject TString(string value) => + value == null ? + JObject.FromObject(new { type = "object", className = "string", subtype = "null" }) : + JObject.FromObject(new { type = "string", value = @value }); + + internal static JObject TNumber(int value) => + JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() }); + + internal static JObject TNumber(uint value) => + JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() }); + + internal static JObject TValueType(string className, string description = null, object members = null) => + JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className }); + + internal static JObject TEnum(string className, string descr, object members = null) => + JObject.FromObject(new { type = "object", isEnum = true, className = className, description = descr }); + + internal static JObject TObject(string className, string description = null, bool is_null = false) => + is_null ? + JObject.FromObject(new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null }) : + JObject.FromObject(new { type = "object", className = className, description = description ?? className }); + + internal static JObject TArray(string className, int length = 0) => JObject.FromObject(new { type = "object", className = className, description = $"{className}({length})", subtype = "array" }); + + internal static JObject TBool(bool value) => JObject.FromObject(new { type = "boolean", value = @value, description = @value ? "true" : "false" }); + + internal static JObject TSymbol(string value) => JObject.FromObject(new { type = "symbol", value = @value, description = @value }); + + /* + For target names with generated method names like + `void b__11_0 (Math.GenericStruct)` + + .. pass target "as `target: "void |(Math.GenericStruct)"` + */ + internal static JObject TDelegate(string className, string target) => JObject.FromObject(new + { + __custom_type = "delegate", + className = className, + target = target + }); + + internal static JObject TPointer(string type_name, bool is_null = false) => JObject.FromObject(new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith("void*") }); + + internal static JObject TIgnore() => JObject.FromObject(new { __custom_type = "ignore_me" }); + + internal static JObject TGetter(string type) => JObject.FromObject(new { __custom_type = "getter", type_name = type }); + + internal static JObject TDateTime(DateTime dt) => JObject.FromObject(new + { + __custom_type = "datetime", + binary = dt.ToBinary() + }); + } + + class DebugTestContext + { + public InspectorClient cli; + public Inspector insp; + public CancellationToken token; + public Dictionary scripts; + + public bool UseCallFunctionOnBeforeGetProperties; + + public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary scripts) + { + this.cli = cli; + this.insp = insp; + this.token = token; + this.scripts = scripts; + } + } + + class DotnetObjectId + { + public string Scheme { get; } + public string Value { get; } + + JObject value_json; + public JObject ValueAsJson + { + get + { + if (value_json == null) + { + try + { + value_json = JObject.Parse(Value); + } + catch (JsonReaderException) { } + } + + return value_json; + } + } + + public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); + + public static bool TryParse(string id, out DotnetObjectId objectId) + { + objectId = null; + if (id == null) + { + return false; + } + + if (!id.StartsWith("dotnet:")) + { + return false; + } + + var parts = id.Split(":", 3); + + if (parts.Length < 3) + { + return false; + } + + objectId = new DotnetObjectId(parts[1], parts[2]); + + return true; + } + + public DotnetObjectId(string scheme, string value) + { + Scheme = scheme; + Value = value; + } + + public override string ToString() => $"dotnet:{Scheme}:{Value}"; + } + + enum StepKind + { + Into, + Over, + Out + } +} diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs new file mode 100644 index 000000000000..96a6c13eb97d --- /dev/null +++ b/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.WebAssembly.Diagnostics +{ + public class TestHarnessOptions : ProxyOptions + { + public string ChromePath { get; set; } + public string AppPath { get; set; } + public string PagePath { get; set; } + public string NodeApp { get; set; } + } +} \ No newline at end of file diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs new file mode 100644 index 000000000000..3995dcc88ded --- /dev/null +++ b/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.WebAssembly.Diagnostics +{ + public class TestHarnessProxy + { + static IWebHost host; + static Task hostTask; + static CancellationTokenSource cts = new CancellationTokenSource(); + static object proxyLock = new object(); + + public static readonly Uri Endpoint = new Uri("http://localhost:9400"); + + public static Task Start(string chromePath, string appPath, string pagePath) + { + lock (proxyLock) + { + if (host != null) + return hostTask; + + host = WebHost.CreateDefaultBuilder() + .UseSetting("UseIISIntegration", false.ToString()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddEnvironmentVariables(prefix: "WASM_TESTS_"); + }) + .ConfigureServices((ctx, services) => + { + services.Configure(ctx.Configuration); + services.Configure(options => + { + options.ChromePath = options.ChromePath ?? chromePath; + options.AppPath = appPath; + options.PagePath = pagePath; + options.DevToolsUrl = new Uri("http://localhost:0"); + }); + }) + .UseStartup() + .UseUrls(Endpoint.ToString()) + .Build(); + hostTask = host.StartAsync(cts.Token); + } + + Console.WriteLine("WebServer Ready!"); + return hostTask; + } + } +} \ No newline at end of file diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs new file mode 100644 index 000000000000..265f1a358164 --- /dev/null +++ b/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs @@ -0,0 +1,255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; + +namespace Microsoft.WebAssembly.Diagnostics +{ + public class TestHarnessStartup + { + static Regex parseConnection = new Regex(@"listening on (ws?s://[^\s]*)"); + public TestHarnessStartup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; set; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddRouting() + .Configure(Configuration); + } + + async Task SendNodeVersion(HttpContext context) + { + Console.WriteLine("hello chrome! json/version"); + var resp_obj = new JObject(); + resp_obj["Browser"] = "node.js/v9.11.1"; + resp_obj["Protocol-Version"] = "1.1"; + + var response = resp_obj.ToString(); + await context.Response.WriteAsync(response, new CancellationTokenSource().Token); + } + + async Task SendNodeList(HttpContext context) + { + Console.WriteLine("hello chrome! json/list"); + try + { + var response = new JArray(JObject.FromObject(new + { + description = "node.js instance", + devtoolsFrontendUrl = "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4", + faviconUrl = "https://nodejs.org/static/favicon.ico", + id = "91d87807-8a81-4f49-878c-a5604103b0a4", + title = "foo.js", + type = "node", + webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4" + })).ToString(); + + Console.WriteLine($"sending: {response}"); + await context.Response.WriteAsync(response, new CancellationTokenSource().Token); + } + catch (Exception e) { Console.WriteLine(e); } + } + + public async Task LaunchAndServe(ProcessStartInfo psi, HttpContext context, Func> extract_conn_url) + { + + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + var tcs = new TaskCompletionSource(); + + var proc = Process.Start(psi); + try + { + proc.ErrorDataReceived += (sender, e) => + { + var str = e.Data; + Console.WriteLine($"stderr: {str}"); + + if (tcs.Task.IsCompleted) + return; + + var match = parseConnection.Match(str); + if (match.Success) + { + tcs.TrySetResult(match.Groups[1].Captures[0].Value); + } + }; + + proc.OutputDataReceived += (sender, e) => + { + Console.WriteLine($"stdout: {e.Data}"); + }; + + proc.BeginErrorReadLine(); + proc.BeginOutputReadLine(); + + if (await Task.WhenAny(tcs.Task, Task.Delay(5000)) != tcs.Task) + { + Console.WriteLine("Didnt get the con string after 5s."); + throw new Exception("node.js timedout"); + } + var line = await tcs.Task; + var con_str = extract_conn_url != null ? await extract_conn_url(line) : line; + + Console.WriteLine($"launching proxy for {con_str}"); + + using var loggerFactory = LoggerFactory.Create( + builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); + var proxy = new DebuggerProxy(loggerFactory); + var browserUri = new Uri(con_str); + var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); + + await proxy.Run(browserUri, ideSocket); + Console.WriteLine("Proxy done"); + } + catch (Exception e) + { + Console.WriteLine("got exception {0}", e); + } + finally + { + proc.CancelErrorRead(); + proc.CancelOutputRead(); + proc.Kill(); + proc.WaitForExit(); + proc.Close(); + } + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env) + { + app.UseWebSockets(); + app.UseStaticFiles(); + + TestHarnessOptions options = optionsAccessor.CurrentValue; + + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".wasm"] = "application/wasm"; + + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(options.AppPath), + ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry: + RequestPath = "", + ContentTypeProvider = provider + }); + + var devToolsUrl = options.DevToolsUrl; + app.UseRouter(router => + { + router.MapGet("launch-chrome-and-connect", async context => + { + Console.WriteLine("New test request"); + try + { + var client = new HttpClient(); + var psi = new ProcessStartInfo(); + + psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}"; + psi.UseShellExecute = false; + psi.FileName = options.ChromePath; + psi.RedirectStandardError = true; + psi.RedirectStandardOutput = true; + + await LaunchAndServe(psi, context, async (str) => + { + var start = DateTime.Now; + JArray obj = null; + + while (true) + { + // Unfortunately it does look like we have to wait + // for a bit after getting the response but before + // making the list request. We get an empty result + // if we make the request too soon. + await Task.Delay(100); + + var res = await client.GetStringAsync(new Uri(new Uri(str), "/json/list")); + Console.WriteLine("res is {0}", res); + + if (!String.IsNullOrEmpty(res)) + { + // Sometimes we seem to get an empty array `[ ]` + obj = JArray.Parse(res); + if (obj != null && obj.Count >= 1) + break; + } + + var elapsed = DateTime.Now - start; + if (elapsed.Milliseconds > 5000) + { + Console.WriteLine($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping"); + return null; + } + } + + var wsURl = obj[0]?["webSocketDebuggerUrl"]?.Value(); + Console.WriteLine(">>> {0}", wsURl); + + return wsURl; + }); + } + catch (Exception ex) + { + Console.WriteLine($"launch-chrome-and-connect failed with {ex.ToString()}"); + } + }); + }); + + if (options.NodeApp != null) + { + Console.WriteLine($"Doing the nodejs: {options.NodeApp}"); + var nodeFullPath = Path.GetFullPath(options.NodeApp); + Console.WriteLine(nodeFullPath); + var psi = new ProcessStartInfo(); + + psi.UseShellExecute = false; + psi.RedirectStandardError = true; + psi.RedirectStandardOutput = true; + + psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}"; + psi.FileName = "node"; + + app.UseRouter(router => + { + //Inspector API for using chrome devtools directly + router.MapGet("json", SendNodeList); + router.MapGet("json/list", SendNodeList); + router.MapGet("json/version", SendNodeVersion); + router.MapGet("launch-done-and-connect", async context => + { + await LaunchAndServe(psi, context, null); + }); + }); + } + } + } +} diff --git a/sdks/wasm/DebuggerTestSuite/Tests.cs b/sdks/wasm/DebuggerTestSuite/Tests.cs index af0ed7219166..34dc5ef605de 100644 --- a/sdks/wasm/DebuggerTestSuite/Tests.cs +++ b/sdks/wasm/DebuggerTestSuite/Tests.cs @@ -1,1378 +1,1528 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; using Newtonsoft.Json.Linq; using Xunit; -using WebAssembly.Net.Debugging; -[assembly: CollectionBehavior (CollectionBehavior.CollectionPerAssembly)] +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] namespace DebuggerTests { - public class SourceList : DebuggerTestBase { - - [Fact] - public async Task CheckThatAllSourcesAreSent () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - //all sources are sent before runtime ready is sent, nothing to check - await insp.Ready (); - Assert.Contains ("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values); - Assert.Contains ("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values); - Assert.Contains ("dotnet://Simple.Dependency.dll/dependency.cs", scripts.Values); - } - - [Fact] - public async Task CreateGoodBreakpoint () { - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var bp1_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2); - - Assert.EndsWith ("debugger-test.cs", bp1_res.Value ["breakpointId"].ToString()); - Assert.Equal (1, bp1_res.Value ["locations"]?.Value ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[0]; - - Assert.NotNull (loc ["scriptId"]); - Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts [loc["scriptId"]?.Value ()]); - Assert.Equal (5, loc ["lineNumber"]); - Assert.Equal (2, loc ["columnNumber"]); - }); - } - - [Fact] - public async Task CreateJSBreakpoint () { - // Test that js breakpoints get set correctly - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint ("/debugger-driver.html", 13, 24); - - Assert.EndsWith ("debugger-driver.html", bp1_res.Value ["breakpointId"].ToString()); - Assert.Equal (1, bp1_res.Value ["locations"]?.Value ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[0]; - - Assert.NotNull (loc ["scriptId"]); - Assert.Equal (13, loc ["lineNumber"]); - Assert.Equal (24, loc ["columnNumber"]); - - var bp2_res = await SetBreakpoint ("/debugger-driver.html", 13, 31); - - Assert.EndsWith ("debugger-driver.html", bp2_res.Value ["breakpointId"].ToString()); - Assert.Equal (1, bp2_res.Value ["locations"]?.Value ()?.Count); - - var loc2 = bp2_res.Value ["locations"]?.Value ()[0]; - - Assert.NotNull (loc2 ["scriptId"]); - Assert.Equal (13, loc2 ["lineNumber"]); - Assert.Equal (31, loc2 ["columnNumber"]); - }); - } - - [Fact] - public async Task CreateJS0Breakpoint () { - // Test that js column 0 does as expected - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint ("/debugger-driver.html", 13, 0); - - Assert.EndsWith ("debugger-driver.html", bp1_res.Value ["breakpointId"].ToString()); - Assert.Equal (1, bp1_res.Value ["locations"]?.Value ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[0]; - - Assert.NotNull (loc ["scriptId"]); - Assert.Equal (13, loc ["lineNumber"]); - Assert.Equal (24, loc ["columnNumber"]); + public class SourceList : DebuggerTestBase + { + + [Fact] + public async Task CheckThatAllSourcesAreSent() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); - var bp2_res = await SetBreakpoint ("/debugger-driver.html", 13, 31); + await Ready(); + //all sources are sent before runtime ready is sent, nothing to check + await insp.Ready(); + Assert.Contains("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values); + Assert.Contains("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values); + Assert.Contains("dotnet://debugger-test.dll/dependency.cs", scripts.Values); + } - Assert.EndsWith ("debugger-driver.html", bp2_res.Value ["breakpointId"].ToString()); - Assert.Equal (1, bp2_res.Value ["locations"]?.Value ()?.Count); + [Fact] + public async Task CreateGoodBreakpoint() + { + var insp = new Inspector(); - var loc2 = bp2_res.Value ["locations"]?.Value ()[0]; + //Collect events + var scripts = SubscribeToScripts(insp); - Assert.NotNull (loc2 ["scriptId"]); - Assert.Equal (13, loc2 ["lineNumber"]); - Assert.Equal (31, loc2 ["columnNumber"]); - }); - } + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - [Theory] - [InlineData (0)] - [InlineData (44)] - public async Task CheckMultipleBreakpointsOnSameLine (int col) { - var insp = new Inspector (); + Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[0]; - var scripts = SubscribeToScripts(insp); + Assert.NotNull(loc["scriptId"]); + Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); + Assert.Equal(10, loc["lineNumber"]); + Assert.Equal(8, loc["columnNumber"]); + }); + } - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); + [Fact] + public async Task CreateJSBreakpoint() + { + // Test that js breakpoints get set correctly + var insp = new Inspector(); - var bp1_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, col); - Assert.EndsWith ("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal (1, bp1_res.Value ["locations"]?.Value ()?.Count); + //Collect events + var scripts = SubscribeToScripts(insp); - var loc = bp1_res.Value ["locations"]?.Value ()[0]; + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24); - CheckLocation ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 44, scripts, loc); + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var bp2_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 49); - Assert.EndsWith ("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal (1, bp2_res.Value ["locations"]?.Value ()?.Count); + var loc = bp1_res.Value["locations"]?.Value()[0]; - var loc2 = bp2_res.Value ["locations"]?.Value ()[0]; + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); + + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); - CheckLocation ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 49, scripts, loc2); - }); - } + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - [Fact] - public async Task CreateBadBreakpoint () { - var insp = new Inspector (); + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - //Collect events - var scripts = SubscribeToScripts(insp); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); + }); + } + + [Fact] + public async Task CreateJS0Breakpoint() + { + // Test that js column 0 does as expected + var insp = new Inspector(); - await Ready (); - await insp.Ready (async (cli, token) => { - var bp1_req = JObject.FromObject(new { - lineNumber = 5, - columnNumber = 2, - url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", - }); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0); + + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[0]; + + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); + + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); + + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - var bp1_res = await cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, token); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); + }); + } - Assert.True (bp1_res.IsOk); - Assert.Empty (bp1_res.Value["locations"].Values()); - //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); - }); - } + [Theory] + [InlineData(0)] + [InlineData(50)] + public async Task CheckMultipleBreakpointsOnSameLine(int col) + { + var insp = new Inspector(); + + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col); + Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[0]; + + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc); + + var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55); + Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + + var loc2 = bp2_res.Value["locations"]?.Value()[0]; + + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2); + }); + } + + [Fact] + public async Task CreateBadBreakpoint() + { + var insp = new Inspector(); + + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + var bp1_req = JObject.FromObject(new + { + lineNumber = 8, + columnNumber = 2, + url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", + }); + + var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); + + Assert.True(bp1_res.IsOk); + Assert.Empty(bp1_res.Value["locations"].Values()); + //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); + }); + } + + [Fact] + public async Task CreateGoodBreakpointAndHit() + { + var insp = new Inspector(); + + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + + var eval_req = JObject.FromObject(new + { + expression = "window.setTimeout(function() { invoke_add(); }, 1);", + }); + + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd", + wait_for_event_fn: (pause_location) => + { + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + Assert.Equal("IntAdd", top_frame["functionName"].Value()); + Assert.Contains("debugger-test.cs", top_frame["url"].Value()); + + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + + //now check the scope + var scope = top_frame["scopeChain"][0]; + Assert.Equal("local", scope["type"]); + Assert.Equal("IntAdd", scope["name"]); + + Assert.Equal("object", scope["object"]["type"]); + Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]); + return Task.CompletedTask; + } + ); + + }); + } + + [Fact] + public async Task ExceptionThrownInJS() + { + var insp = new Inspector(); + + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + var eval_req = JObject.FromObject(new + { + expression = "invoke_bad_js_test();" + }); + + var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); + Assert.True(eval_res.IsErr); + Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value()); + }); + } + + [Fact] + public async Task ExceptionThrownInJSOutOfBand() + { + var insp = new Inspector(); + + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetBreakpoint("/debugger-driver.html", 27, 2); + + var eval_req = JObject.FromObject(new + { + expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", + }); + + var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); + // Response here will be the id for the timer from JS! + Assert.True(eval_res.IsOk); + + var ex = await Assert.ThrowsAsync(async () => await insp.WaitFor("Runtime.exceptionThrown")); + var ex_json = JObject.Parse(ex.Message); + Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); + }); + + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsAtBreakpointSite(bool use_cfo) => + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd", + "window.setTimeout(function() { invoke_add(); }, 1);", + use_cfo: use_cfo, + test_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 0); + CheckNumber(locals, "e", 0); + } + ); + + [Fact] + public async Task InspectPrimitiveTypeLocalsAtBreakpointSite() => + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", 154, 8, "PrimitiveTypesTest", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:PrimitiveTypesTest'); }, 1);", + test_fn: (locals) => + { + CheckSymbol(locals, "c0", "8364 '€'"); + CheckSymbol(locals, "c1", "65 'A'"); + } + ); + + [Fact] + public async Task InspectLocalsTypesAtBreakpointSite() => + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test2.cs", 48, 8, "Types", + "window.setTimeout(function() { invoke_static_method (\"[debugger-test] Fancy:Types\")(); }, 1);", + use_cfo: false, + test_fn: (locals) => + { + CheckNumber(locals, "dPI", Math.PI); + CheckNumber(locals, "fPI", (float)Math.PI); + CheckNumber(locals, "iMax", int.MaxValue); + CheckNumber(locals, "iMin", int.MinValue); + CheckNumber(locals, "uiMax", uint.MaxValue); + CheckNumber(locals, "uiMin", uint.MinValue); + + CheckNumber(locals, "l", uint.MaxValue * (long)2); + //CheckNumber (locals, "lMax", long.MaxValue); // cannot be represented as double + //CheckNumber (locals, "lMin", long.MinValue); // cannot be represented as double + + CheckNumber(locals, "sbMax", sbyte.MaxValue); + CheckNumber(locals, "sbMin", sbyte.MinValue); + CheckNumber(locals, "bMax", byte.MaxValue); + CheckNumber(locals, "bMin", byte.MinValue); + + CheckNumber(locals, "sMax", short.MaxValue); + CheckNumber(locals, "sMin", short.MinValue); + CheckNumber(locals, "usMin", ushort.MinValue); + CheckNumber(locals, "usMax", ushort.MaxValue); + } + ); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsWithGenericTypesAtBreakpointSite(bool use_cfo) => + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", 74, 8, "GenericTypesTest", + "window.setTimeout(function() { invoke_generic_types_test (); }, 1);", + use_cfo: use_cfo, + test_fn: (locals) => + { + CheckObject(locals, "list", "System.Collections.Generic.Dictionary"); + CheckObject(locals, "list_null", "System.Collections.Generic.Dictionary", is_null: true); + + CheckArray(locals, "list_arr", "System.Collections.Generic.Dictionary[]", 1); + CheckObject(locals, "list_arr_null", "System.Collections.Generic.Dictionary[]", is_null: true); + + // Unused locals + CheckObject(locals, "list_unused", "System.Collections.Generic.Dictionary"); + CheckObject(locals, "list_null_unused", "System.Collections.Generic.Dictionary", is_null: true); + + CheckArray(locals, "list_arr_unused", "System.Collections.Generic.Dictionary[]", 1); + CheckObject(locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary[]", is_null: true); + } + ); + + object TGenericStruct(string typearg, string stringField) => new + { + List = TObject($"System.Collections.Generic.List<{typearg}>"), + StringField = TString(stringField) + }; + + [Fact] + public async Task RuntimeGetPropertiesWithInvalidScopeIdTest() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8); + + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_delegates_test (); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 49, 8, + "DelegatesTest", + wait_for_event_fn: async (pause_location) => + { + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + + var scope = top_frame["scopeChain"][0]; + Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]); + + // Try to get an invalid scope! + var get_prop_req = JObject.FromObject(new + { + objectId = "dotnet:scope:23490871", + }); + + var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); + Assert.True(frame_props.IsErr); + } + ); + }); + } + + [Fact] + public async Task TrivalStepping() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd", + wait_for_event_fn: (pause_location) => + { + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + return Task.CompletedTask; + } + ); + + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 11, 8, "IntAdd", + wait_for_event_fn: (pause_location) => + { + var top_frame = pause_location["callFrames"][0]; + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + return Task.CompletedTask; + } + ); + }); + } + + [Fact] + public async Task InspectLocalsDuringStepping() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(debugger_test_loc, 10, 8); + + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + debugger_test_loc, 10, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 0); + CheckNumber(locals, "e", 0); + } + ); + + await StepAndCheck(StepKind.Over, debugger_test_loc, 11, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 50); + CheckNumber(locals, "e", 0); + } + ); + + //step and get locals + await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 50); + CheckNumber(locals, "e", 60); + } + ); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsInPreviousFramesDuringSteppingIn2(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + var dep_cs_loc = "dotnet://debugger-test.dll/dependency.cs"; + await SetBreakpoint(dep_cs_loc, 33, 8); + + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + + // Will stop in Complex.DoEvenMoreStuff + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_use_complex (); }, 1);", + dep_cs_loc, 33, 8, "DoEvenMoreStuff", + locals_fn: (locals) => + { + Assert.Single(locals); + CheckObject(locals, "this", "Simple.Complex"); + } + ); + + var props = await GetObjectOnFrame(pause_location["callFrames"][0], "this"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckObject(props, "c", "object"); + + // Check UseComplex frame + var locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][3], debugger_test_loc, 23, 8, "UseComplex"); + Assert.Equal(7, locals_m1.Count()); + + CheckNumber(locals_m1, "a", 10); + CheckNumber(locals_m1, "b", 20); + CheckObject(locals_m1, "complex", "Simple.Complex"); + CheckNumber(locals_m1, "c", 30); + CheckNumber(locals_m1, "d", 50); + CheckNumber(locals_m1, "e", 60); + CheckNumber(locals_m1, "f", 0); + + props = await GetObjectOnFrame(pause_location["callFrames"][3], "complex"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckObject(props, "c", "object"); + + pause_location = await StepAndCheck(StepKind.Over, dep_cs_loc, 23, 8, "DoStuff", times: 2); + // Check UseComplex frame again + locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][1], debugger_test_loc, 23, 8, "UseComplex"); + Assert.Equal(7, locals_m1.Count()); + + CheckNumber(locals_m1, "a", 10); + CheckNumber(locals_m1, "b", 20); + CheckObject(locals_m1, "complex", "Simple.Complex"); + CheckNumber(locals_m1, "c", 30); + CheckNumber(locals_m1, "d", 50); + CheckNumber(locals_m1, "e", 60); + CheckNumber(locals_m1, "f", 0); + + props = await GetObjectOnFrame(pause_location["callFrames"][1], "complex"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckObject(props, "c", "object"); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsInPreviousFramesDuringSteppingIn(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(debugger_test_loc, 111, 12); + + // Will stop in InnerMethod + var wait_res = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_outer_method(); }, 1);", + debugger_test_loc, 111, 12, "InnerMethod", + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + CheckNumber(locals, "i", 5); + CheckNumber(locals, "j", 24); + CheckString(locals, "foo_str", "foo"); + CheckObject(locals, "this", "Math.NestedInMath"); + } + ); + + var this_props = await GetObjectOnFrame(wait_res["callFrames"][0], "this"); + Assert.Equal(2, this_props.Count()); + CheckObject(this_props, "m", "Math"); + CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); + + var ss_props = await GetObjectOnLocals(this_props, "SimpleStructProperty"); + var dt = new DateTime(2020, 1, 2, 3, 4, 5); + await CheckProps(ss_props, new + { + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("Math.GenericStruct") + }, "ss_props"); + + await CheckDateTime(ss_props, "dt", new DateTime(2020, 1, 2, 3, 4, 5)); + + // Check OuterMethod frame + var locals_m1 = await GetLocalsForFrame(wait_res["callFrames"][1], debugger_test_loc, 87, 8, "OuterMethod"); + Assert.Equal(5, locals_m1.Count()); + // FIXME: Failing test CheckNumber (locals_m1, "i", 5); + // FIXME: Failing test CheckString (locals_m1, "text", "Hello"); + CheckNumber(locals_m1, "new_i", 0); + CheckNumber(locals_m1, "k", 0); + CheckObject(locals_m1, "nim", "Math.NestedInMath"); + + // step back into OuterMethod + await StepAndCheck(StepKind.Over, debugger_test_loc, 91, 8, "OuterMethod", times: 9, + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); + + // FIXME: Failing test CheckNumber (locals_m1, "i", 5); + CheckString(locals, "text", "Hello"); + // FIXME: Failing test CheckNumber (locals, "new_i", 24); + CheckNumber(locals, "k", 19); + CheckObject(locals, "nim", "Math.NestedInMath"); + } + ); + + //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", times: 2); + + // step into InnerMethod2 + await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 4, "InnerMethod2", + locals_fn: (locals) => + { + Assert.Equal(3, locals.Count()); + + CheckString(locals, "s", "test string"); + //out var: CheckNumber (locals, "k", 0); + CheckNumber(locals, "i", 24); + } + ); + + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 100, 4, "InnerMethod2", times: 4, + locals_fn: (locals) => + { + Assert.Equal(3, locals.Count()); + + CheckString(locals, "s", "test string"); + // FIXME: Failing test CheckNumber (locals, "k", 34); + CheckNumber(locals, "i", 24); + } + ); + + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 92, 8, "OuterMethod", times: 2, + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); + + CheckString(locals, "text", "Hello"); + // FIXME: failing test CheckNumber (locals, "i", 5); + CheckNumber(locals, "new_i", 22); + CheckNumber(locals, "k", 34); + CheckObject(locals, "nim", "Math.NestedInMath"); + } + ); + }); + } + + [Fact] + public async Task InspectLocalsDuringSteppingIn() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 86, 8); + + await EvaluateAndCheck("window.setTimeout(function() { invoke_outer_method(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 86, 8, "OuterMethod", + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); + + CheckObject(locals, "nim", "Math.NestedInMath"); + CheckNumber(locals, "i", 5); + CheckNumber(locals, "k", 0); + CheckNumber(locals, "new_i", 0); + CheckString(locals, "text", null); + } + ); + + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 87, 8, "OuterMethod", + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); + + CheckObject(locals, "nim", "Math.NestedInMath"); + // FIXME: Failing test CheckNumber (locals, "i", 5); + CheckNumber(locals, "k", 0); + CheckNumber(locals, "new_i", 0); + CheckString(locals, "text", "Hello"); + } + ); + + // Step into InnerMethod + await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 105, 8, "InnerMethod"); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 109, 12, "InnerMethod", times: 5, + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + + CheckNumber(locals, "i", 5); + CheckNumber(locals, "j", 15); + CheckString(locals, "foo_str", "foo"); + CheckObject(locals, "this", "Math.NestedInMath"); + } + ); + + // Step back to OuterMethod + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 88, 8, "OuterMethod", times: 6, + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); + + CheckObject(locals, "nim", "Math.NestedInMath"); + // FIXME: Failing test CheckNumber (locals, "i", 5); + CheckNumber(locals, "k", 0); + CheckNumber(locals, "new_i", 24); + CheckString(locals, "text", "Hello"); + } + ); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsInAsyncMethods(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + + await SetBreakpoint(debugger_test_loc, 120, 12); + await SetBreakpoint(debugger_test_loc, 135, 12); + + // Will stop in Asyncmethod0 + var wait_res = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);", + debugger_test_loc, 120, 12, "MoveNext", //FIXME: + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + CheckString(locals, "s", "string from js"); + CheckNumber(locals, "i", 42); + CheckString(locals, "local0", "value0"); + CheckObject(locals, "this", "Math.NestedInMath"); + } + ); + Console.WriteLine(wait_res); - [Fact] - public async Task CreateGoodBreakpointAndHit () { - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2); - - var eval_req = JObject.FromObject(new { - expression = "window.setTimeout(function() { invoke_add(); }, 1);", - }); - - await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_add(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 5, 2, - "IntAdd", - wait_for_event_fn: (pause_location) => { - Assert.Equal ("other", pause_location ["reason"]?.Value ()); - Assert.Equal (bp.Value["breakpointId"]?.ToString(), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - Assert.Equal ("IntAdd", top_frame ["functionName"].Value()); - Assert.Contains ("debugger-test.cs", top_frame ["url"].Value ()); - - CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]); - - //now check the scope - var scope = top_frame ["scopeChain"][0]; - Assert.Equal ("local", scope ["type"]); - Assert.Equal ("IntAdd", scope ["name"]); - - Assert.Equal ("object", scope ["object"]["type"]); - Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]); - CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, scope["startLocation"]); - CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 9, 1, scripts, scope["endLocation"]); - return Task.CompletedTask; - } - ); - - }); - } - - [Fact] - public async Task ExceptionThrownInJS () { - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - var eval_req = JObject.FromObject(new { - expression = "invoke_bad_js_test();" - }); - - var eval_res = await cli.SendCommand ("Runtime.evaluate", eval_req, token); - Assert.True (eval_res.IsErr); - Assert.Equal ("Uncaught", eval_res.Error ["exceptionDetails"]? ["text"]? .Value ()); - }); - } - - [Fact] - public async Task ExceptionThrownInJSOutOfBand () { - var insp = new Inspector (); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - await SetBreakpoint ("/debugger-driver.html", 27, 2); - - var eval_req = JObject.FromObject(new { - expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", - }); - - var eval_res = await cli.SendCommand ("Runtime.evaluate", eval_req, token); - // Response here will be the id for the timer from JS! - Assert.True (eval_res.IsOk); - - var ex = await Assert.ThrowsAsync (async () => await insp.WaitFor("Runtime.exceptionThrown")); - var ex_json = JObject.Parse (ex.Message); - Assert.Equal (dicFileToUrl["/debugger-driver.html"], ex_json ["exceptionDetails"]? ["url"]? .Value ()); - }); - - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsAtBreakpointSite (bool use_cfo) => - await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", 5, 2, "IntAdd", - "window.setTimeout(function() { invoke_add(); }, 1);", - use_cfo: use_cfo, - test_fn: (locals) => { - CheckNumber (locals, "a", 10); - CheckNumber (locals, "b", 20); - CheckNumber (locals, "c", 30); - CheckNumber (locals, "d", 0); - CheckNumber (locals, "e", 0); - } - ); - - [Fact] - public async Task InspectPrimitiveTypeLocalsAtBreakpointSite () => - await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", 145, 2, "PrimitiveTypesTest", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:PrimitiveTypesTest'); }, 1);", - test_fn: (locals) => { - CheckSymbol (locals, "c0", "8364 '€'"); - CheckSymbol (locals, "c1", "65 'A'"); - } - ); - - [Fact] - public async Task InspectLocalsTypesAtBreakpointSite () => - await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test2.cs", 40, 2, "Types", - "window.setTimeout(function() { invoke_static_method (\"[debugger-test] Fancy:Types\")(); }, 1);", - use_cfo: false, - test_fn: (locals) => { - CheckNumber (locals, "dPI", Math.PI); - CheckNumber (locals, "fPI", (float)Math.PI); - CheckNumber (locals, "iMax", int.MaxValue); - CheckNumber (locals, "iMin", int.MinValue); - CheckNumber (locals, "uiMax", uint.MaxValue); - CheckNumber (locals, "uiMin", uint.MinValue); - - CheckNumber (locals, "l", uint.MaxValue * (long)2); - //CheckNumber (locals, "lMax", long.MaxValue); // cannot be represented as double - //CheckNumber (locals, "lMin", long.MinValue); // cannot be represented as double - - CheckNumber (locals, "sbMax", sbyte.MaxValue); - CheckNumber (locals, "sbMin", sbyte.MinValue); - CheckNumber (locals, "bMax", byte.MaxValue); - CheckNumber (locals, "bMin", byte.MinValue); - - CheckNumber (locals, "sMax", short.MaxValue); - CheckNumber (locals, "sMin", short.MinValue); - CheckNumber (locals, "usMin", ushort.MinValue); - CheckNumber (locals, "usMax", ushort.MaxValue); - } - ); - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsWithGenericTypesAtBreakpointSite (bool use_cfo) => - await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-test.cs", 65, 2, "GenericTypesTest", - "window.setTimeout(function() { invoke_generic_types_test (); }, 1);", - use_cfo: use_cfo, - test_fn: (locals) => { - CheckObject (locals, "list", "System.Collections.Generic.Dictionary"); - CheckObject (locals, "list_null", "System.Collections.Generic.Dictionary", is_null: true); - - CheckArray (locals, "list_arr", "System.Collections.Generic.Dictionary[]"); - CheckObject (locals, "list_arr_null", "System.Collections.Generic.Dictionary[]", is_null: true); - - // Unused locals - CheckObject (locals, "list_unused", "System.Collections.Generic.Dictionary"); - CheckObject (locals, "list_null_unused", "System.Collections.Generic.Dictionary", is_null: true); - - CheckObject (locals, "list_arr_unused", "System.Collections.Generic.Dictionary[]"); - CheckObject (locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary[]", is_null: true); - } - ); - - object TGenericStruct(string typearg, string stringField) - => new { - List = TObject ($"System.Collections.Generic.List<{typearg}>"), - StringField = TString (stringField) - }; - - [Fact] - public async Task RuntimeGetPropertiesWithInvalidScopeIdTest () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 41, 2); - - await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_delegates_test (); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 41, 2, - "DelegatesTest", - wait_for_event_fn: async (pause_location) => { - //make sure we're on the right bp - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - - var scope = top_frame ["scopeChain"][0]; - Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]); - - // Try to get an invalid scope! - var get_prop_req = JObject.FromObject(new { - objectId = "dotnet:scope:23490871", - }); - - var frame_props = await cli.SendCommand ("Runtime.getProperties", get_prop_req, token); - Assert.True (frame_props.IsErr); - } - ); - }); - } - - [Fact] - public async Task TrivalStepping () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2); - - await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_add(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 5, 2, - "IntAdd", - wait_for_event_fn: (pause_location) => { - //make sure we're on the right bp - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]); - return Task.CompletedTask; - } - ); - - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 6, 2, "IntAdd", - wait_for_event_fn: (pause_location) => { - var top_frame = pause_location ["callFrames"][0]; - CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]); - return Task.CompletedTask; - } - ); - }); - } - - [Fact] - public async Task InspectLocalsDuringStepping () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint (debugger_test_loc, 4, 2); - - await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_add(); }, 1);", - debugger_test_loc, 4, 2, "IntAdd", - locals_fn: (locals) => { - CheckNumber (locals, "a", 10); - CheckNumber (locals, "b", 20); - CheckNumber (locals, "c", 0); - CheckNumber (locals, "d", 0); - CheckNumber (locals, "e", 0); - } - ); - - await StepAndCheck (StepKind.Over, debugger_test_loc, 5, 2, "IntAdd", - locals_fn: (locals) => { - CheckNumber (locals, "a", 10); - CheckNumber (locals, "b", 20); - CheckNumber (locals, "c", 30); - CheckNumber (locals, "d", 0); - CheckNumber (locals, "e", 0); - } - ); - - //step and get locals - await StepAndCheck (StepKind.Over, debugger_test_loc, 6, 2, "IntAdd", - locals_fn: (locals) => { - CheckNumber (locals, "a", 10); - CheckNumber (locals, "b", 20); - CheckNumber (locals, "c", 30); - CheckNumber (locals, "d", 50); - CheckNumber (locals, "e", 0); - } - ); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsInPreviousFramesDuringSteppingIn2 (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - var dep_cs_loc = "dotnet://Simple.Dependency.dll/dependency.cs"; - await SetBreakpoint (dep_cs_loc, 24, 2); - - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - - // Will stop in Complex.DoEvenMoreStuff - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_use_complex (); }, 1);", - dep_cs_loc, 24, 2, "DoEvenMoreStuff", - locals_fn: (locals) => { - Assert.Single (locals); - CheckObject (locals, "this", "Simple.Complex"); - } - ); - - var props = await GetObjectOnFrame (pause_location["callFrames"][0], "this"); - Assert.Equal (3, props.Count()); - CheckNumber (props, "A", 10); - CheckString (props, "B", "xx"); - CheckObject (props, "c", "object"); - - // Check UseComplex frame - var locals_m1 = await GetLocalsForFrame (pause_location ["callFrames"][3], debugger_test_loc, 17, 2, "UseComplex"); - Assert.Equal (7, locals_m1.Count()); - - CheckNumber (locals_m1, "a", 10); - CheckNumber (locals_m1, "b", 20); - CheckObject (locals_m1, "complex", "Simple.Complex"); - CheckNumber (locals_m1, "c", 30); - CheckNumber (locals_m1, "d", 50); - CheckNumber (locals_m1, "e", 60); - CheckNumber (locals_m1, "f", 0); - - props = await GetObjectOnFrame (pause_location["callFrames"][3], "complex"); - Assert.Equal (3, props.Count()); - CheckNumber (props, "A", 10); - CheckString (props, "B", "xx"); - CheckObject (props, "c", "object"); - - pause_location = await StepAndCheck (StepKind.Over, dep_cs_loc, 16, 2, "DoStuff", times: 2); - // Check UseComplex frame again - locals_m1 = await GetLocalsForFrame (pause_location ["callFrames"][1], debugger_test_loc, 17, 2, "UseComplex"); - Assert.Equal (7, locals_m1.Count()); - - CheckNumber (locals_m1, "a", 10); - CheckNumber (locals_m1, "b", 20); - CheckObject (locals_m1, "complex", "Simple.Complex"); - CheckNumber (locals_m1, "c", 30); - CheckNumber (locals_m1, "d", 50); - CheckNumber (locals_m1, "e", 60); - CheckNumber (locals_m1, "f", 0); - - props = await GetObjectOnFrame (pause_location["callFrames"][1], "complex"); - Assert.Equal (3, props.Count()); - CheckNumber (props, "A", 10); - CheckString (props, "B", "xx"); - CheckObject (props, "c", "object"); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsInPreviousFramesDuringSteppingIn (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint (debugger_test_loc, 102, 3); - - // Will stop in InnerMethod - var wait_res = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_outer_method(); }, 1);", - debugger_test_loc, 102, 3, "InnerMethod", - locals_fn: (locals) => { - Assert.Equal (4, locals.Count()); - CheckNumber (locals, "i", 5); - CheckNumber (locals, "j", 24); - CheckString (locals, "foo_str", "foo"); - CheckObject (locals, "this", "Math.NestedInMath"); - } - ); - - var this_props = await GetObjectOnFrame (wait_res["callFrames"][0], "this"); - Assert.Equal (2, this_props.Count()); - CheckObject (this_props, "m", "Math"); - CheckValueType (this_props, "SimpleStructProperty", "Math.SimpleStruct"); - - var ss_props = await GetObjectOnLocals (this_props, "SimpleStructProperty"); - Assert.Equal (2, ss_props.Count()); - CheckValueType (ss_props, "dt", "System.DateTime"); - CheckValueType (ss_props, "gs", "Math.GenericStruct"); - - await CheckDateTime (ss_props, "dt", new DateTime (2020, 1, 2, 3, 4, 5)); - - // Check OuterMethod frame - var locals_m1 = await GetLocalsForFrame (wait_res ["callFrames"][1], debugger_test_loc, 78, 2, "OuterMethod"); - Assert.Equal (5, locals_m1.Count()); - // FIXME: Failing test CheckNumber (locals_m1, "i", 5); - // FIXME: Failing test CheckString (locals_m1, "text", "Hello"); - CheckNumber (locals_m1, "new_i", 0); - CheckNumber (locals_m1, "k", 0); - CheckObject (locals_m1, "nim", "Math.NestedInMath"); - - // step back into OuterMethod - await StepAndCheck (StepKind.Over, debugger_test_loc, 82, 2, "OuterMethod", times: 9, - locals_fn: (locals) => { - Assert.Equal (5, locals.Count()); - - // FIXME: Failing test CheckNumber (locals_m1, "i", 5); - CheckString (locals, "text", "Hello"); - // FIXME: Failing test CheckNumber (locals, "new_i", 24); - CheckNumber (locals, "k", 19); - CheckObject (locals, "nim", "Math.NestedInMath"); - } - ); - - //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", times: 2); - - // step into InnerMethod2 - await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 87, 1, "InnerMethod2", - locals_fn: (locals) => { - Assert.Equal (3, locals.Count()); - - CheckString (locals, "s", "test string"); - //out var: CheckNumber (locals, "k", 0); - CheckNumber (locals, "i", 24); - } - ); - - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 91, 1, "InnerMethod2", times: 4, - locals_fn: (locals) => { - Assert.Equal (3, locals.Count()); - - CheckString (locals, "s", "test string"); - // FIXME: Failing test CheckNumber (locals, "k", 34); - CheckNumber (locals, "i", 24); - } - ); - - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 83, 2, "OuterMethod", times: 2, - locals_fn: (locals) => { - Assert.Equal (5, locals.Count()); - - CheckString (locals, "text", "Hello"); - // FIXME: failing test CheckNumber (locals, "i", 5); - CheckNumber (locals, "new_i", 22); - CheckNumber (locals, "k", 34); - CheckObject (locals, "nim", "Math.NestedInMath"); - } - ); - }); - } - - [Fact] - public async Task InspectLocalsDuringSteppingIn () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 77, 2); - - await EvaluateAndCheck ("window.setTimeout(function() { invoke_outer_method(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 77, 2, "OuterMethod", - locals_fn: (locals) => { - Assert.Equal (5, locals.Count()); - - CheckObject (locals, "nim", "Math.NestedInMath"); - CheckNumber (locals, "i", 5); - CheckNumber (locals, "k", 0); - CheckNumber (locals, "new_i", 0); - CheckString (locals, "text", null); - } - ); - - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 78, 2, "OuterMethod", - locals_fn: (locals) => { - Assert.Equal (5, locals.Count()); - - CheckObject (locals, "nim", "Math.NestedInMath"); - // FIXME: Failing test CheckNumber (locals, "i", 5); - CheckNumber (locals, "k", 0); - CheckNumber (locals, "new_i", 0); - CheckString (locals, "text", "Hello"); - } - ); - - // Step into InnerMethod - await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 2, "InnerMethod"); - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 100, 3, "InnerMethod", times: 5, - locals_fn: (locals) => { - Assert.Equal (4, locals.Count()); - - CheckNumber (locals, "i", 5); - CheckNumber (locals, "j", 15); - CheckString (locals, "foo_str", "foo"); - CheckObject (locals, "this", "Math.NestedInMath"); - } - ); - - // Step back to OuterMethod - await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 79, 2, "OuterMethod", times: 6, - locals_fn: (locals) => { - Assert.Equal (5, locals.Count()); - - CheckObject (locals, "nim", "Math.NestedInMath"); - // FIXME: Failing test CheckNumber (locals, "i", 5); - CheckNumber (locals, "k", 0); - CheckNumber (locals, "new_i", 24); - CheckString (locals, "text", "Hello"); - } - ); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsInAsyncMethods (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - - await SetBreakpoint (debugger_test_loc, 111, 3); - await SetBreakpoint (debugger_test_loc, 126, 3); - - // Will stop in Asyncmethod0 - var wait_res = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);", - debugger_test_loc, 111, 3, "MoveNext", //FIXME: - locals_fn: (locals) => { - Assert.Equal (4, locals.Count()); - CheckString (locals, "s", "string from js"); - CheckNumber (locals, "i", 42); - CheckString (locals, "local0", "value0"); - CheckObject (locals, "this", "Math.NestedInMath"); - } - ); - Console.WriteLine (wait_res); - #if false // Disabled for now, as we don't have proper async traces - var locals = await GetProperties (wait_res ["callFrames"][2]["callFrameId"].Value ()); - Assert.Equal (4, locals.Count()); - CheckString (locals, "ls", "string from jstest"); - CheckNumber (locals, "li", 52); + var locals = await GetProperties(wait_res["callFrames"][2]["callFrameId"].Value()); + Assert.Equal(4, locals.Count()); + CheckString(locals, "ls", "string from jstest"); + CheckNumber(locals, "li", 52); #endif - // TODO: previous frames have async machinery details, so no point checking that right now - - var pause_loc = await SendCommandAndCheck (null, "Debugger.resume", debugger_test_loc, 126, 3, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext", - locals_fn: (locals) => { - Assert.Equal (4, locals.Count()); - CheckString (locals, "str", "AsyncMethodNoReturn's local"); - CheckObject (locals, "this", "Math.NestedInMath"); - //FIXME: check fields - CheckValueType (locals, "ss", "Math.SimpleStruct"); - CheckArray (locals, "ss_arr", "Math.SimpleStruct[]"); - // TODO: struct fields - } - ); - - var this_props = await GetObjectOnFrame (pause_loc ["callFrames"][0], "this"); - Assert.Equal (2, this_props.Count ()); - CheckObject (this_props, "m", "Math"); - CheckValueType (this_props, "SimpleStructProperty", "Math.SimpleStruct"); - - // TODO: Check `this` properties - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsWithStructs (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint (debugger_test_loc, 16, 2); - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", - debugger_test_loc, 16, 2, "MethodWithLocalStructs", - locals_fn: (locals) => { - Assert.Equal (3, locals.Count ()); - - CheckValueType (locals, "ss_local", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckValueType (locals, "gs_local", "DebuggerTests.ValueTypesTest.GenericStruct"); - CheckObject (locals, "vt_local", "DebuggerTests.ValueTypesTest"); - } - ); - - var dt = new DateTime (2021, 2, 3, 4, 6, 7); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_local"); - await CheckProps (ss_local_props, new { - str_member = TString ("set in MethodWithLocalStructs#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Utc") - }, "ss_local"); - - { - // Check ss_local.dt - await CheckDateTime (ss_local_props, "dt", dt); - - // Check ss_local.gs - var gs_props = await GetObjectOnLocals (ss_local_props, "gs"); - CheckString (gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); - CheckObject (gs_props, "List", "System.Collections.Generic.List"); - } - - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "gs_local"); - await CheckProps (gs_local_props, new { - StringField = TString ("gs_local#GenericStruct#StringField"), - List = TObject ("System.Collections.Generic.List", is_null: true), - Options = TEnum ("DebuggerTests.Options", "None") - }, "gs_local"); - - // Check vt_local's properties - var vt_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "vt_local"); - Assert.Equal (5, vt_local_props.Count()); - - CheckString (vt_local_props, "StringField", "string#0"); - CheckValueType (vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckValueType (vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); - await CheckDateTime (vt_local_props, "DT", new DateTime (2020, 1, 2, 3, 4, 5)); - CheckEnum (vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); - - { - // SimpleStructProperty - dt = new DateTime (2022, 3, 4, 5, 7, 8); - var ssp_props = await CompareObjectPropertiesFor (vt_local_props, "SimpleStructProperty", - new { - str_member = TString ("SimpleStructProperty#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Utc") - }, - label: "vt_local_props.SimpleStructProperty"); - - await CheckDateTime (ssp_props, "dt", dt); - - // SimpleStructField - dt = new DateTime (2025, 6, 7, 8, 10, 11); - var ssf_props = await CompareObjectPropertiesFor (vt_local_props, "SimpleStructField", - new { - str_member = TString ("SimpleStructField#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Local") - }, - label: "vt_local_props.SimpleStructField"); - - await CheckDateTime (ssf_props, "dt", dt); - } - - // FIXME: check ss_local.gs.List's members - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectValueTypeMethodArgs (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint (debugger_test_loc, 27, 3); - - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);", - debugger_test_loc, 27, 3, "MethodWithStructArgs", - locals_fn: (locals) => { - Assert.Equal (3, locals.Count ()); - - CheckString (locals, "label", "TestStructsAsMethodArgs#label"); - CheckValueType (locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckNumber (locals, "x", 3); - } - ); - - var dt = new DateTime (2025, 6, 7, 8, 10, 11); - var ss_local_as_ss_arg = new { - str_member = TString ("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Local") - }; - var ss_local_gs = new { - StringField = TString ("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"), - List = TObject ("System.Collections.Generic.List"), - Options = TEnum ("DebuggerTests.Options", "Option1") - }; - - // Check ss_arg's properties - var ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_arg"); - await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_arg"); - - { - // Check ss_local.dt - await CheckDateTime (ss_arg_props, "dt", dt); - - // Check ss_local.gs - await CompareObjectPropertiesFor (ss_arg_props, "gs", ss_local_gs); - } - - pause_location = await StepAndCheck (StepKind.Over, debugger_test_loc, 31, 3, "MethodWithStructArgs", times: 4, - locals_fn: (locals) => { - Assert.Equal (3, locals.Count()); - - CheckString (locals, "label", "TestStructsAsMethodArgs#label"); - CheckValueType (locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckNumber (locals, "x", 3); - - } - ); - - var ss_arg_updated = new { - str_member = TString ("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Utc") - }; - - ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_arg"); - await CheckProps (ss_arg_props, ss_arg_updated, "ss_ar"); - - { - // Check ss_local.gs - await CompareObjectPropertiesFor (ss_arg_props, "gs", new { - StringField = TString ("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"), - List = TObject ("System.Collections.Generic.List"), - Options = TEnum ("DebuggerTests.Options", "Option1") - }); - - await CheckDateTime (ss_arg_props, "dt", dt); - } - - // Check locals on previous frame, same as earlier in this test - ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][1], "ss_local"); - await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_local"); - - { - // Check ss_local.dt - await CheckDateTime (ss_arg_props, "dt", dt); - - // Check ss_local.gs - var gs_props = await GetObjectOnLocals (ss_arg_props, "gs"); - CheckString (gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"); - CheckObject (gs_props, "List", "System.Collections.Generic.List"); - } - - // ----------- Step back to the caller --------- - - pause_location = await StepAndCheck (StepKind.Over, debugger_test_loc, 22, 3, "TestStructsAsMethodArgs", - times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */} ); - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckProps (locals, new { - ss_local = TValueType ("DebuggerTests.ValueTypesTest.SimpleStruct"), - ss_ret = TValueType ("DebuggerTests.ValueTypesTest.SimpleStruct") - }, - "locals#0"); - - ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"] [0], "ss_local"); - await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_local"); - - { - // Check ss_local.gs - await CompareObjectPropertiesFor (ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs"); - } - - // FIXME: check ss_local.gs.List's members - }); - } - - [Fact] - public async Task CheckUpdatedValueTypeFieldsOnResume () - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - var lines = new [] {186, 189}; - await SetBreakpoint (debugger_test_loc, lines [0], 3); - await SetBreakpoint (debugger_test_loc, lines [1], 3); - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);", - debugger_test_loc, lines [0], 3, "MethodUpdatingValueTypeMembers"); - - var dt = new DateTime (1, 2, 3, 4, 5, 6); - await CheckLocals (pause_location, dt); - - // Resume - dt = new DateTime (9, 8, 7, 6, 5, 4); - pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MethodUpdatingValueTypeMembers"); - await CheckLocals (pause_location, dt); - }); - - async Task CheckLocals (JToken pause_location, DateTime dt) - { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckProps (locals, new { - obj = TObject ("DebuggerTests.ClassForToStringTests"), - vt = TObject ("DebuggerTests.StructForToStringTests") - }, "locals"); - - var obj_props = await GetObjectOnLocals (locals, "obj"); - { - await CheckProps (obj_props, new { - DT = TValueType ("System.DateTime", dt.ToString ()) - }, "locals#obj.DT", num_fields: 5); - - await CheckDateTime (obj_props, "DT", dt); - } - - var vt_props = await GetObjectOnLocals (locals, "obj"); - { - await CheckProps (vt_props, new { - DT = TValueType ("System.DateTime", dt.ToString ()) - }, "locals#obj.DT", num_fields: 5); - - await CheckDateTime (vt_props, "DT", dt); - } - } - } - - [Fact] - public async Task CheckUpdatedValueTypeLocalsOnResumeAsync () - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - var lines = new [] { 195, 197 }; - await SetBreakpoint (debugger_test_loc, lines [0], 3); - await SetBreakpoint (debugger_test_loc, lines [1], 3); - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);", - debugger_test_loc, lines [0], 3, "MoveNext"); - - var dt = new DateTime (1, 2, 3, 4, 5, 6); - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckDateTime (locals, "dt", dt); - - // Resume - dt = new DateTime (9, 8, 7, 6, 5, 4); - pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MoveNext"); - locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckDateTime (locals, "dt", dt); - }); - } - - [Fact] - public async Task CheckUpdatedVTArrayMembersOnResume () - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - var lines = new [] { 205, 207 }; - await SetBreakpoint (debugger_test_loc, lines [0], 3); - await SetBreakpoint (debugger_test_loc, lines [1], 3); - - var dt = new DateTime (1, 2, 3, 4, 5, 6); - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);", - debugger_test_loc, lines [0], 3, "MethodUpdatingVTArrayMembers"); - await CheckArrayElements (pause_location, dt); - - // Resume - dt = new DateTime (9, 8, 7, 6, 5, 4); - pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MethodUpdatingVTArrayMembers"); - await CheckArrayElements (pause_location, dt); - }); - - async Task CheckArrayElements (JToken pause_location, DateTime dt) - { - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckProps (locals, new { - ssta = TArray ("DebuggerTests.StructForToStringTests[]", 1) - }, "locals"); - - var ssta = await GetObjectOnLocals (locals, "ssta"); - var sst0 = await GetObjectOnLocals (ssta, "0"); - await CheckProps (sst0, new { - DT = TValueType ("System.DateTime", dt.ToString ()) - }, "dta [0]", num_fields: 5); - - await CheckDateTime (sst0, "DT", dt); - } - } - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsWithStructsStaticAsync (bool use_cfo) { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint (debugger_test_loc, 47, 3); - - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method_async (" - + "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" - + "); }, 1);", - debugger_test_loc, 47, 3, "MoveNext"); //BUG: method name - - var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value ()); - await CheckProps (locals, new { - ss_local = TObject ("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - result = TBool (true) - }, - "locals#0"); - - var dt = new DateTime (2021, 2, 3, 4, 6, 7); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_local"); - await CheckProps (ss_local_props, new { - str_member = TString ("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime", dt.ToString ()), - gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum ("System.DateTimeKind", "Utc") - }, "ss_local"); - - { - // Check ss_local.dt - await CheckDateTime (ss_local_props, "dt", dt); - - // Check ss_local.gs - await CompareObjectPropertiesFor (ss_local_props, "gs", - new { - StringField = TString ("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), - List = TObject ("System.Collections.Generic.List"), - Options = TEnum ("DebuggerTests.Options", "Option1") - } - ); - } - - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "gs_local"); - await CheckProps (gs_local_props, new { - StringField = TString ("gs_local#GenericStruct#StringField"), - List = TObject ("System.Collections.Generic.List"), - Options = TEnum ("DebuggerTests.Options", "Option2") - }, "gs_local"); - - // FIXME: check ss_local.gs.List's members - }); - } - - [Theory] - [InlineData (123, 3, "MethodWithLocalsForToStringTest", false, false)] - [InlineData (133, 3, "MethodWithArgumentsForToStringTest", true, false)] - [InlineData (175, 3, "MethodWithArgumentsForToStringTestAsync", true, true)] - [InlineData (165, 3, "MethodWithArgumentsForToStringTestAsync", false, true)] - public async Task InspectLocalsForToStringDescriptions (int line, int col, string method_name, bool call_other, bool invoke_async) - { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}"; - int frame_idx = 0; - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint (debugger_test_loc, line, col); - - var eval_expr = "window.setTimeout(function() {" - + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") - + $"'{entry_method_name}'," - + (call_other ? "true" : "false") - + "); }, 1);"; - Console.WriteLine ($"{eval_expr}"); - - var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); - - var dt0 = new DateTime (2020, 1, 2, 3, 4, 5); - var dt1 = new DateTime (2010, 5, 4, 3, 2, 1); - var ts = dt0 - dt1; - var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0)); - - var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value ()); - await CheckProps (frame_locals, new { - call_other = TBool (call_other), - dt0 = TValueType ("System.DateTime", dt0.ToString ()), - dt1 = TValueType ("System.DateTime", dt1.ToString ()), - dto = TValueType ("System.DateTimeOffset", dto.ToString ()), - ts = TValueType ("System.TimeSpan", ts.ToString ()), - dec = TValueType ("System.Decimal", "123987123"), - guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), - dts = TArray ("System.DateTime[]", 2), - obj = TObject ("DebuggerTests.ClassForToStringTests"), - sst = TObject ("DebuggerTests.StructForToStringTests") - }, "locals#0"); - - var dts_0 = new DateTime (1983, 6, 7, 5, 6, 10); - var dts_1 = new DateTime (1999, 10, 15, 1, 2, 3); - var dts_elements = await GetObjectOnLocals (frame_locals, "dts"); - await CheckDateTime (dts_elements, "0", dts_0); - await CheckDateTime (dts_elements, "1", dts_1); - - // TimeSpan - await CompareObjectPropertiesFor (frame_locals, "ts", - new { - Days = TNumber (3530), - Minutes = TNumber (2), - Seconds = TNumber (4), - }, "ts_props", num_fields: 12); - - // DateTimeOffset - await CompareObjectPropertiesFor (frame_locals, "dto", - new { - Day = TNumber (2), - Year = TNumber (2020), - DayOfWeek = TEnum ("System.DayOfWeek", "Thursday") - }, "dto_props", num_fields: 22); - - var DT = new DateTime (2004, 10, 15, 1, 2, 3); - var DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)); - - var obj_props = await CompareObjectPropertiesFor (frame_locals, "obj", - new { - DT = TValueType ("System.DateTime", DT.ToString ()), - DTO = TValueType ("System.DateTimeOffset", DTO.ToString ()), - TS = TValueType ("System.TimeSpan", ts.ToString ()), - Dec = TValueType ("System.Decimal", "1239871"), - Guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "obj_props"); - - DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)); - var sst_props = await CompareObjectPropertiesFor (frame_locals, "sst", - new { - DT = TValueType ("System.DateTime", DT.ToString ()), - DTO = TValueType ("System.DateTimeOffset", DTO.ToString ()), - TS = TValueType ("System.TimeSpan", ts.ToString ()), - Dec = TValueType ("System.Decimal", "1239871"), - Guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "sst_props"); - }); - } - - [Fact] - public async Task InspectLocals () { - var insp = new Inspector (); - var scripts = SubscribeToScripts (insp); - - await Ready(); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var wait_res = await RunUntil ("locals_inner"); - var locals = await GetProperties (wait_res ["callFrames"][1]["callFrameId"].Value ()); - }); - } - - [Theory] - [InlineData (false)] - [InlineData (true)] - public async Task InspectLocalsForStructInstanceMethod (bool use_cfo) - => await CheckInspectLocalsAtBreakpointSite ( - "dotnet://debugger-test.dll/debugger-array-test.cs", 236, 3, - "GenericInstanceMethod", - "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EntryClass:run'); })", - use_cfo: use_cfo, - wait_for_event_fn: async (pause_location) => { - var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value()); - - await CheckProps (frame_locals, new { - sc_arg = TObject ("DebuggerTests.SimpleClass"), - @this = TValueType ("DebuggerTests.Point"), - local_gs = TValueType ("DebuggerTests.SimpleGenericStruct") - }, - "locals#0"); - - await CompareObjectPropertiesFor (frame_locals, "local_gs", - new { - Id = TString ("local_gs#Id"), - Color = TEnum ("DebuggerTests.RGB", "Green"), - Value = TNumber (4) - }, - label: "local_gs#0"); - - await CompareObjectPropertiesFor (frame_locals, "sc_arg", - TSimpleClass (10, 45, "sc_arg#Id", "Blue"), - label: "sc_arg#0"); - - await CompareObjectPropertiesFor (frame_locals, "this", - TPoint (90, -4, "point#Id", "Green"), - label: "this#0"); - - }); - - [Fact] - public async Task SteppingIntoMscorlib () { - var insp = new Inspector (); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready (); - await insp.Ready (async (cli, token) => { - ctx = new DebugTestContext (cli, insp, token, scripts); - - var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 74, 2); - var pause_location = await EvaluateAndCheck ( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 74, 2, - "OuterMethod"); - - //make sure we're on the right bp - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - pause_location = await SendCommandAndCheck (null, $"Debugger.stepInto", null, -1, -1, null); - var top_frame = pause_location ["callFrames"][0]; - - AssertEqual ("WriteLine", top_frame ["functionName"]?.Value (), "Expected to be in WriteLine method"); - var script_id = top_frame ["functionLocation"]["scriptId"].Value (); - AssertEqual ("dotnet://mscorlib.dll/Console.cs", scripts [script_id], "Expected to stopped in System.Console.WriteLine"); - }); - } - - //TODO add tests covering basic stepping behavior as step in/out/over - } + // TODO: previous frames have async machinery details, so no point checking that right now + + var pause_loc = await SendCommandAndCheck(null, "Debugger.resume", debugger_test_loc, 135, 12, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext", + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + CheckString(locals, "str", "AsyncMethodNoReturn's local"); + CheckObject(locals, "this", "Math.NestedInMath"); + //FIXME: check fields + CheckValueType(locals, "ss", "Math.SimpleStruct"); + CheckArray(locals, "ss_arr", "Math.SimpleStruct[]", 0); + // TODO: struct fields + } + ); + + var this_props = await GetObjectOnFrame(pause_loc["callFrames"][0], "this"); + Assert.Equal(2, this_props.Count()); + CheckObject(this_props, "m", "Math"); + CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); + + // TODO: Check `this` properties + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsWithStructs(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + await SetBreakpoint(debugger_test_loc, 24, 8); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", + debugger_test_loc, 24, 8, "MethodWithLocalStructs"); + + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + vt_local = TObject("DebuggerTests.ValueTypesTest") + }, "locals"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local"); + Assert.Equal(5, vt_local_props.Count()); + + CheckString(vt_local_props, "StringField", "string#0"); + CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); + await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5)); + CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); + + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); + + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); + // Check ss_local.dt + await CheckDateTime(ss_local_props, "dt", dt); + + // Check ss_local.gs + var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); + CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); + CheckObject(gs_props, "List", "System.Collections.Generic.List"); + } + + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List", is_null: true), + Options = TEnum("DebuggerTests.Options", "None") + }, "gs_local"); + + // Check vt_local's properties + + var exp = new[] + { + ("SimpleStructProperty", 2, "Utc"), + ("SimpleStructField", 5, "Local") + }; + + foreach (var (name, bias, dt_kind) in exp) + { + dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); + var ssp_props = await CompareObjectPropertiesFor(vt_local_props, name, + new + { + V = TGetter("V"), + str_member = TString($"{name}#string#0#SimpleStruct#str_member"), + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", dt_kind) + }, + label: $"vt_local_props.{name}"); + + await CheckDateTime(ssp_props, "dt", dt); + var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); + } + + // FIXME: check ss_local.gs.List's members + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectValueTypeMethodArgs(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + await SetBreakpoint(debugger_test_loc, 36, 12); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);", + debugger_test_loc, 36, 12, "MethodWithStructArgs"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + { + Assert.Equal(3, locals.Count()); + CheckString(locals, "label", "TestStructsAsMethodArgs#label"); + CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckNumber(locals, "x", 3); + } + + var dt = new DateTime(2025, 6, 7, 8, 10, 11); + var ss_local_as_ss_arg = new + { + V = TGetter("V"), + str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Local") + }; + var ss_local_gs = new + { + StringField = TString("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + }; + + // Check ss_arg's properties + var ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_arg"); + + var res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); + await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); + + { + // Check ss_local.dt + await CheckDateTime(ss_arg_props, "dt", dt); + + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs); + } + + pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 40, 8, "MethodWithStructArgs", times: 4, + locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + { + Assert.Equal(3, locals.Count()); + + CheckString(locals, "label", "TestStructsAsMethodArgs#label"); + CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckNumber(locals, "x", 3); + } + + var ss_arg_updated = new + { + V = TGetter("V"), + str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }; + + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); + await CheckProps(ss_arg_props, ss_arg_updated, "ss_arg"); + + res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); + await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); + + { + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", new + { + StringField = TString("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + }); + + await CheckDateTime(ss_arg_props, "dt", dt); + } + + // Check locals on previous frame, same as earlier in this test + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][1], "ss_local"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); + + { + // Check ss_local.dt + await CheckDateTime(ss_arg_props, "dt", dt); + + // Check ss_local.gs + var gs_props = await GetObjectOnLocals(ss_arg_props, "gs"); + CheckString(gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"); + CheckObject(gs_props, "List", "System.Collections.Generic.List"); + } + + // ----------- Step back to the caller --------- + + pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 30, 12, "TestStructsAsMethodArgs", + times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + ss_ret = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct") + }, + "locals#0"); + + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); + + { + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs"); + } + + // FIXME: check ss_local.gs.List's members + }); + } + + [Fact] + public async Task CheckUpdatedValueTypeFieldsOnResume() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + var lines = new[] { 205, 208 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);", + debugger_test_loc, lines[0], 12, "MethodUpdatingValueTypeMembers"); + + await CheckLocals(pause_location, new DateTime(1, 2, 3, 4, 5, 6), new DateTime(4, 5, 6, 7, 8, 9)); + + // Resume + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingValueTypeMembers"); + await CheckLocals(pause_location, new DateTime(9, 8, 7, 6, 5, 4), new DateTime(5, 1, 3, 7, 9, 10)); + }); + + async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt) + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + obj = TObject("DebuggerTests.ClassForToStringTests"), + vt = TObject("DebuggerTests.StructForToStringTests") + }, "locals"); + + var obj_props = await GetObjectOnLocals(locals, "obj"); + { + await CheckProps(obj_props, new + { + DT = TValueType("System.DateTime", obj_dt.ToString()) + }, "locals#obj.DT", num_fields: 5); + + await CheckDateTime(obj_props, "DT", obj_dt); + } + + var vt_props = await GetObjectOnLocals(locals, "vt"); + { + await CheckProps(vt_props, new + { + DT = TValueType("System.DateTime", vt_dt.ToString()) + }, "locals#obj.DT", num_fields: 5); + + await CheckDateTime(vt_props, "DT", vt_dt); + } + } + } + + [Fact] + public async Task CheckUpdatedValueTypeLocalsOnResumeAsync() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + var lines = new[] { 214, 216 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);", + debugger_test_loc, lines[0], 12, "MoveNext"); + + var dt = new DateTime(1, 2, 3, 4, 5, 6); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckDateTime(locals, "dt", dt); + + // Resume + dt = new DateTime(9, 8, 7, 6, 5, 4); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MoveNext"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckDateTime(locals, "dt", dt); + }); + } + + [Fact] + public async Task CheckUpdatedVTArrayMembersOnResume() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + var lines = new[] { 225, 227 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); + + var dt = new DateTime(1, 2, 3, 4, 5, 6); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);", + debugger_test_loc, lines[0], 12, "MethodUpdatingVTArrayMembers"); + await CheckArrayElements(pause_location, dt); + + // Resume + dt = new DateTime(9, 8, 7, 6, 5, 4); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingVTArrayMembers"); + await CheckArrayElements(pause_location, dt); + }); + + async Task CheckArrayElements(JToken pause_location, DateTime dt) + { + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ssta = TArray("DebuggerTests.StructForToStringTests[]", 1) + }, "locals"); + + var ssta = await GetObjectOnLocals(locals, "ssta"); + var sst0 = await GetObjectOnLocals(ssta, "0"); + await CheckProps(sst0, new + { + DT = TValueType("System.DateTime", dt.ToString()) + }, "dta [0]", num_fields: 5); + + await CheckDateTime(sst0, "DT", dt); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + await SetBreakpoint(debugger_test_loc, 54, 12); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async (" + + "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" + + "); }, 1);", + debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name + + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + result = TBool(true) + }, + "locals#0"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), + dt = TValueType("System.DateTime", dt.ToString()), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); + + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); + + // Check ss_local.dt + await CheckDateTime(ss_local_props, "dt", dt); + + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_local_props, "gs", + new + { + StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + } + ); + } + + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option2") + }, "gs_local"); + + // FIXME: check ss_local.gs.List's members + }); + } + + [Theory] + [InlineData(137, 12, "MethodWithLocalsForToStringTest", false, false)] + [InlineData(147, 12, "MethodWithArgumentsForToStringTest", true, false)] + [InlineData(192, 12, "MethodWithArgumentsForToStringTestAsync", true, true)] + [InlineData(182, 12, "MethodWithArgumentsForToStringTestAsync", false, true)] + public async Task InspectLocalsForToStringDescriptions(int line, int col, string method_name, bool call_other, bool invoke_async) + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}"; + int frame_idx = 0; + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + await SetBreakpoint(debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() {" + + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + + $"'{entry_method_name}'," + + (call_other ? "true" : "false") + + "); }, 1);"; + Console.WriteLine($"{eval_expr}"); + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); + + var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(call_other), + dt0 = TValueType("System.DateTime", dt0.ToString()), + dt1 = TValueType("System.DateTime", dt1.ToString()), + dto = TValueType("System.DateTimeOffset", dto.ToString()), + ts = TValueType("System.TimeSpan", ts.ToString()), + dec = TValueType("System.Decimal", "123987123"), + guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), + dts = TArray("System.DateTime[]", 2), + obj = TObject("DebuggerTests.ClassForToStringTests"), + sst = TObject("DebuggerTests.StructForToStringTests") + }, "locals#0"); + + var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10); + var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3); + var dts_elements = await GetObjectOnLocals(frame_locals, "dts"); + await CheckDateTime(dts_elements, "0", dts_0); + await CheckDateTime(dts_elements, "1", dts_1); + + // TimeSpan + await CompareObjectPropertiesFor(frame_locals, "ts", + new + { + Days = TNumber(3530), + Minutes = TNumber(2), + Seconds = TNumber(4), + }, "ts_props", num_fields: 12); + + // DateTimeOffset + await CompareObjectPropertiesFor(frame_locals, "dto", + new + { + Day = TNumber(2), + Year = TNumber(2020), + DayOfWeek = TEnum("System.DayOfWeek", "Thursday") + }, "dto_props", num_fields: 22); + + var DT = new DateTime(2004, 10, 15, 1, 2, 3); + var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); + + var obj_props = await CompareObjectPropertiesFor(frame_locals, "obj", + new + { + DT = TValueType("System.DateTime", DT.ToString()), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "obj_props"); + + DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)); + var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", + new + { + DT = TValueType("System.DateTime", DT.ToString()), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "sst_props"); + }); + } + + [Fact] + public async Task InspectLocals() + { + var insp = new Inspector(); + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var wait_res = await RunUntil("locals_inner"); + var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value()); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InspectLocalsForStructInstanceMethod(bool use_cfo) => await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-array-test.cs", 258, 12, + "GenericInstanceMethod", + "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EntryClass:run'); })", + use_cfo: use_cfo, + wait_for_event_fn: async (pause_location) => + { + var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "locals#0"); + + await CompareObjectPropertiesFor(frame_locals, "local_gs", + new + { + Id = TString("local_gs#Id"), + Color = TEnum("DebuggerTests.RGB", "Green"), + Value = TNumber(4) + }, + label: "local_gs#0"); + + await CompareObjectPropertiesFor(frame_locals, "sc_arg", + TSimpleClass(10, 45, "sc_arg#Id", "Blue"), + label: "sc_arg#0"); + + await CompareObjectPropertiesFor(frame_locals, "this", + TPoint(90, -4, "point#Id", "Green"), + label: "this#0"); + + }); + + [Fact] + public async Task SteppingIntoMscorlib() + { + var insp = new Inspector(); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready(async (cli, token) => + { + ctx = new DebugTestContext(cli, insp, token, scripts); + + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 83, 8); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 83, 8, + "OuterMethod"); + + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + pause_location = await SendCommandAndCheck(null, $"Debugger.stepInto", null, -1, -1, null); + var top_frame = pause_location["callFrames"][0]; + + AssertEqual("WriteLine", top_frame["functionName"]?.Value(), "Expected to be in WriteLine method"); + var script_id = top_frame["functionLocation"]["scriptId"].Value(); + Assert.Matches ("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]); + }); + } + + [Fact] + public async Task InvalidValueTypeData() + { + await CheckInspectLocalsAtBreakpointSite( + "dotnet://debugger-test.dll/debugger-test.cs", 85, 8, + "OuterMethod", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); })", + wait_for_event_fn: async (pause_location) => + { + var new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 4 }});"); + await _invoke_getter(new_id, "NonExistant", expect_ok: false); + + new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3 }});"); + await _invoke_getter(new_id, "NonExistant", expect_ok: false); + + new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 'AA' }});"); + await _invoke_getter(new_id, "NonExistant", expect_ok: false); + }); + + async Task CreateNewId(string expr) + { + var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed"); + AssertEqual("string", res.Value["result"]?["type"]?.Value(), "Expected Runtime.evaluate to return a string type result"); + return res.Value["result"]?["value"]?.Value(); + } + + async Task _invoke_getter(string obj_id, string property_name, bool expect_ok) + { + var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')"; + var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}"); + + return res; + } + } + + //TODO add tests covering basic stepping behavior as step in/out/over + } } diff --git a/sdks/wasm/Makefile b/sdks/wasm/Makefile index 29553fcd7b2a..22711c7b67b7 100644 --- a/sdks/wasm/Makefile +++ b/sdks/wasm/Makefile @@ -467,7 +467,7 @@ $(BROWSER_TEST_DYNAMIC)/.stamp-browser-test-dynamic-suite: packager.exe $(WASM_F touch $@ bin/debugger-test-suite/dotnet.wasm: packager.exe binding_tests.dll debugger-test.dll tests/debugger/debugger-driver.html other.js builds/debug/dotnet.js - $(PACKAGER) --copy=always -debugrt -debug --template=runtime.js --builddir=obj/debugger-test-suite --appdir=bin/debugger-test-suite --asset=tests/debugger/debugger-driver.html --asset=other.js debugger-test.dll + $(PACKAGER) --copy=always -debugrt -debug --template=runtime.js --template-output-name=runtime-debugger.js --builddir=obj/debugger-test-suite --appdir=bin/debugger-test-suite --asset=tests/debugger/debugger-driver.html --asset=other.js debugger-test.dll ninja -v -C obj/debugger-test-suite touch $@ @@ -674,7 +674,7 @@ build-debugger-test-app: bin/debugger-test-suite/dotnet.wasm build-managed: build-debug-sample build-test-suite build-dbg-proxy: - $(DOTNET_BUILD) ProxyDriver + $(DOTNET_BUILD) BrowserDebugHost build-dbg-testsuite: $(DOTNET_BUILD) DebuggerTestSuite @@ -791,10 +791,10 @@ package: build build-sdk build-dbg-proxy cp $(OPTIONS_CS) tmp/ cp packager.exe tmp/ cp runtime.js tmp/ - cp Mono.WebAssembly.DebuggerProxy/bin/Debug/netstandard2.1/Mono.WebAssembly.DebuggerProxy.dll tmp/ - cp Mono.WebAssembly.DebuggerProxy/bin/Debug/netstandard2.1/Mono.WebAssembly.DebuggerProxy.pdb tmp/ + cp BrowserDebugProxy/bin/Debug/netcoreapp3.0/BrowserDebugProxy.dll tmp/ + cp BrowserDebugProxy/bin/Debug/netcoreapp3.0/BrowserDebugProxy.pdb tmp/ mkdir tmp/dbg-proxy - cp -r ProxyDriver/bin/Debug/netcoreapp3.0/ tmp/dbg-proxy/ + cp -r BrowserDebugHost/bin/Debug/netcoreapp3.0/ tmp/dbg-proxy/ mkdir tmp/docs cp -r ./docs/ tmp/docs/ mkdir tmp/packages diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/.editorconfig b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/.editorconfig deleted file mode 100644 index ca41c33264af..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/.editorconfig +++ /dev/null @@ -1,69 +0,0 @@ -root = true - -[*] -end_of_line = crlf -tab_width = 4 -trim_trailing_whitespace = true -insert_final_newline = true - -[{*.cs, *.tt}] -indent_style = tab -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_switch_labels = false -csharp_new_line_before_catch = false -csharp_new_line_before_else = false -csharp_new_line_before_finally = false -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = methods -csharp_prefer_braces = false -csharp_preserve_single_line_statements = false -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = true -csharp_space_around_declaration_statements = do_not_ignore -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_open_square_brackets = true -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_name_and_opening_parenthesis = true -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = true -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false -csharp_style_conditional_delegate_call = true -csharp_style_expression_bodied_accessors = true -csharp_style_expression_bodied_constructors = true -csharp_style_expression_bodied_indexers = true -csharp_style_expression_bodied_methods = true -csharp_style_expression_bodied_operators = true -csharp_style_expression_bodied_properties = true -csharp_style_inlined_variable_declaration = true -csharp_style_pattern_matching_over_as_with_null_check = true -csharp_style_pattern_matching_over_is_with_cast_check = true -csharp_style_throw_expression = true - -[{*.ts, *.json}] -indent_style = tab - -[*.csproj] -indent_style = tab -tab_width = 2 -indent_size = 2 - -[*.js] -indent_style = tab - -[*.yml] -indent_style = tab - -[*.md] -indent_style = tab diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/AssemblyInfo.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/AssemblyInfo.cs deleted file mode 100644 index 8e935c7ed0d0..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo ("DebuggerTestSuite")] diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs deleted file mode 100644 index f633fe786d59..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs +++ /dev/null @@ -1,828 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; -using System.Linq; -using Newtonsoft.Json.Linq; -using System.Net.Http; -using Mono.Cecil.Pdb; -using Newtonsoft.Json; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Threading; -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; - -namespace WebAssembly.Net.Debugging { - internal class BreakpointRequest { - public string Id { get; private set; } - public string Assembly { get; private set; } - public string File { get; private set; } - public int Line { get; private set; } - public int Column { get; private set; } - public MethodInfo Method { get; private set; } - - JObject request; - - public bool IsResolved => Assembly != null; - public List Locations { get; } = new List (); - - public override string ToString () - => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; - - public object AsSetBreakpointByUrlResponse (IEnumerable jsloc) - => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()).Concat (jsloc) }; - - public BreakpointRequest () { - } - - public BreakpointRequest (string id, MethodInfo method) { - Id = id; - Method = method; - } - - public BreakpointRequest (string id, JObject request) { - Id = id; - this.request = request; - } - - public static BreakpointRequest Parse (string id, JObject args) - { - return new BreakpointRequest (id, args); - } - - public BreakpointRequest Clone () - => new BreakpointRequest { Id = Id, request = request }; - - public bool IsMatch (SourceFile sourceFile) - { - var url = request? ["url"]?.Value (); - if (url == null) { - var urlRegex = request?["urlRegex"].Value(); - var regex = new Regex (urlRegex); - return regex.IsMatch (sourceFile.Url.ToString ()) || regex.IsMatch (sourceFile.DocUrl); - } - - return sourceFile.Url.ToString () == url || sourceFile.DotNetUrl == url; - } - - public bool TryResolve (SourceFile sourceFile) - { - if (!IsMatch (sourceFile)) - return false; - - var line = request? ["lineNumber"]?.Value (); - var column = request? ["columnNumber"]?.Value (); - - if (line == null || column == null) - return false; - - Assembly = sourceFile.AssemblyName; - File = sourceFile.DebuggerFileName; - Line = line.Value; - Column = column.Value; - return true; - } - - public bool TryResolve (DebugStore store) - { - if (request == null || store == null) - return false; - - return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null; - } - } - - internal class VarInfo { - public VarInfo (VariableDebugInformation v) - { - this.Name = v.Name; - this.Index = v.Index; - } - - public VarInfo (ParameterDefinition p) - { - this.Name = p.Name; - this.Index = (p.Index + 1) * -1; - } - - public string Name { get; } - public int Index { get; } - - public override string ToString () - => $"(var-info [{Index}] '{Name}')"; - } - - internal class CliLocation { - public CliLocation (MethodInfo method, int offset) - { - Method = method; - Offset = offset; - } - - public MethodInfo Method { get; } - public int Offset { get; } - } - - internal class SourceLocation { - SourceId id; - int line; - int column; - CliLocation cliLoc; - - public SourceLocation (SourceId id, int line, int column) - { - this.id = id; - this.line = line; - this.column = column; - } - - public SourceLocation (MethodInfo mi, SequencePoint sp) - { - this.id = mi.SourceId; - this.line = sp.StartLine - 1; - this.column = sp.StartColumn - 1; - this.cliLoc = new CliLocation (mi, sp.Offset); - } - - public SourceId Id { get => id; } - public int Line { get => line; } - public int Column { get => column; } - public CliLocation CliLocation => this.cliLoc; - - public override string ToString () - => $"{id}:{Line}:{Column}"; - - public static SourceLocation Parse (JObject obj) - { - if (obj == null) - return null; - - if (!SourceId.TryParse (obj ["scriptId"]?.Value (), out var id)) - return null; - - var line = obj ["lineNumber"]?.Value (); - var column = obj ["columnNumber"]?.Value (); - if (id == null || line == null || column == null) - return null; - - return new SourceLocation (id, line.Value, column.Value); - } - - - internal class LocationComparer : EqualityComparer - { - public override bool Equals (SourceLocation l1, SourceLocation l2) - { - if (l1 == null && l2 == null) - return true; - else if (l1 == null || l2 == null) - return false; - - return (l1.Line == l2.Line && - l1.Column == l2.Column && - l1.Id == l2.Id); - } - - public override int GetHashCode (SourceLocation loc) - { - int hCode = loc.Line ^ loc.Column; - return loc.Id.GetHashCode () ^ hCode.GetHashCode (); - } - } - - internal object AsLocation () - => new { - scriptId = id.ToString (), - lineNumber = line, - columnNumber = column - }; - } - - internal class SourceId { - const string Scheme = "dotnet://"; - - readonly int assembly, document; - - public int Assembly => assembly; - public int Document => document; - - internal SourceId (int assembly, int document) - { - this.assembly = assembly; - this.document = document; - } - - public SourceId (string id) - { - if (!TryParse (id, out assembly, out document)) - throw new ArgumentException ("invalid source identifier", nameof (id)); - } - - public static bool TryParse (string id, out SourceId source) - { - source = null; - if (!TryParse (id, out var assembly, out var document)) - return false; - - source = new SourceId (assembly, document); - return true; - } - - static bool TryParse (string id, out int assembly, out int document) - { - assembly = document = 0; - if (id == null || !id.StartsWith (Scheme, StringComparison.Ordinal)) - return false; - - var sp = id.Substring (Scheme.Length).Split ('_'); - if (sp.Length != 2) - return false; - - if (!int.TryParse (sp [0], out assembly)) - return false; - - if (!int.TryParse (sp [1], out document)) - return false; - - return true; - } - - public override string ToString () - => $"{Scheme}{assembly}_{document}"; - - public override bool Equals (object obj) - { - if (obj == null) - return false; - SourceId that = obj as SourceId; - return that.assembly == this.assembly && that.document == this.document; - } - - public override int GetHashCode () - => assembly.GetHashCode () ^ document.GetHashCode (); - - public static bool operator == (SourceId a, SourceId b) - => ((object)a == null) ? (object)b == null : a.Equals (b); - - public static bool operator != (SourceId a, SourceId b) - => !a.Equals (b); - } - - internal class MethodInfo { - MethodDefinition methodDef; - SourceFile source; - - public SourceId SourceId => source.SourceId; - - public string Name => methodDef.Name; - public MethodDebugInformation DebugInformation => methodDef.DebugInformation; - - public SourceLocation StartLocation { get; } - public SourceLocation EndLocation { get; } - public AssemblyInfo Assembly { get; } - public uint Token => methodDef.MetadataToken.RID; - - public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) - { - this.Assembly = assembly; - this.methodDef = methodDef; - this.source = source; - - var sps = DebugInformation.SequencePoints; - if (sps == null || sps.Count() < 1) - return; - - SequencePoint start = sps [0]; - SequencePoint end = sps [0]; - - foreach (var sp in sps) { - if (sp.StartLine < start.StartLine) - start = sp; - else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn) - start = sp; - - if (sp.EndLine > end.EndLine) - end = sp; - else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn) - end = sp; - } - - StartLocation = new SourceLocation (this, start); - EndLocation = new SourceLocation (this, end); - } - - public SourceLocation GetLocationByIl (int pos) - { - SequencePoint prev = null; - foreach (var sp in DebugInformation.SequencePoints) { - if (sp.Offset > pos) - break; - prev = sp; - } - - if (prev != null) - return new SourceLocation (this, prev); - - return null; - } - - public VarInfo [] GetLiveVarsAt (int offset) - { - var res = new List (); - - res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p))); - res.AddRange (methodDef.DebugInformation.GetScopes () - .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) - .SelectMany (s => s.Variables) - .Where (v => !v.IsDebuggerHidden) - .Select (v => new VarInfo (v))); - - return res.ToArray (); - } - - public override string ToString () => "MethodInfo(" + methodDef.FullName + ")"; - } - - internal class TypeInfo { - AssemblyInfo assembly; - TypeDefinition type; - List methods; - - public TypeInfo (AssemblyInfo assembly, TypeDefinition type) { - this.assembly = assembly; - this.type = type; - methods = new List (); - } - - public string Name => type.Name; - public string FullName => type.FullName; - public List Methods => methods; - - public override string ToString () => "TypeInfo('" + FullName + "')"; - } - - class AssemblyInfo { - static int next_id; - ModuleDefinition image; - readonly int id; - readonly ILogger logger; - Dictionary methods = new Dictionary (); - Dictionary sourceLinkMappings = new Dictionary(); - Dictionary typesByName = new Dictionary (); - readonly List sources = new List(); - internal string Url { get; } - - public AssemblyInfo (IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb) - { - this.id = Interlocked.Increment (ref next_id); - - try { - Url = url; - ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); - rp.AssemblyResolver = resolver; - // set ReadSymbols = true unconditionally in case there - // is an embedded pdb then handle ArgumentException - // and assume that if pdb == null that is the cause - rp.ReadSymbols = true; - rp.SymbolReaderProvider = new PdbReaderProvider (); - if (pdb != null) - rp.SymbolStream = new MemoryStream (pdb); - rp.ReadingMode = ReadingMode.Immediate; - - this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); - } catch (BadImageFormatException ex) { - logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}"); - } catch (ArgumentException) { - // if pdb == null this is expected and we - // read the assembly without symbols below - if (pdb != null) - throw; - } - - if (this.image == null) { - ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); - rp.AssemblyResolver = resolver; - if (pdb != null) { - rp.ReadSymbols = true; - rp.SymbolReaderProvider = new PdbReaderProvider (); - rp.SymbolStream = new MemoryStream (pdb); - } - - rp.ReadingMode = ReadingMode.Immediate; - - this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); - } - - Populate (); - } - - public AssemblyInfo (ILogger logger) - { - this.logger = logger; - } - - void Populate () - { - ProcessSourceLink(); - - var d2s = new Dictionary (); - - SourceFile FindSource (Document doc) - { - if (doc == null) - return null; - - if (d2s.TryGetValue (doc, out var source)) - return source; - - var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url)); - sources.Add (src); - d2s [doc] = src; - return src; - }; - - foreach (var type in image.GetTypes()) { - var typeInfo = new TypeInfo (this, type); - typesByName [type.FullName] = typeInfo; - - foreach (var method in type.Methods) { - foreach (var sp in method.DebugInformation.SequencePoints) { - var source = FindSource (sp.Document); - var methodInfo = new MethodInfo (this, method, source); - methods [method.MetadataToken.RID] = methodInfo; - if (source != null) - source.AddMethod (methodInfo); - - typeInfo.Methods.Add (methodInfo); - } - } - } - } - - private void ProcessSourceLink () - { - var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink); - - if (sourceLinkDebugInfo != null) { - var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; - - if (sourceLinkContent != null) { - var jObject = JObject.Parse (sourceLinkContent) ["documents"]; - sourceLinkMappings = JsonConvert.DeserializeObject> (jObject.ToString ()); - } - } - } - - private Uri GetSourceLinkUrl (string document) - { - if (sourceLinkMappings.TryGetValue (document, out string url)) - return new Uri (url); - - foreach (var sourceLinkDocument in sourceLinkMappings) { - string key = sourceLinkDocument.Key; - - if (Path.GetFileName (key) != "*") { - continue; - } - - var keyTrim = key.TrimEnd ('*'); - - if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) { - var docUrlPart = document.Replace (keyTrim, ""); - return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart); - } - } - - return null; - } - - public IEnumerable Sources - => this.sources; - - public Dictionary TypesByName => this.typesByName; - public int Id => id; - public string Name => image.Name; - - public SourceFile GetDocById (int document) - { - return sources.FirstOrDefault (s => s.SourceId.Document == document); - } - - public MethodInfo GetMethodByToken (uint token) - { - methods.TryGetValue (token, out var value); - return value; - } - - public TypeInfo GetTypeByName (string name) { - typesByName.TryGetValue (name, out var res); - return res; - } - } - - internal class SourceFile { - Dictionary methods; - AssemblyInfo assembly; - int id; - Document doc; - - internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) - { - this.methods = new Dictionary (); - this.SourceLinkUri = sourceLinkUri; - this.assembly = assembly; - this.id = id; - this.doc = doc; - this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", ""); - - this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute); - if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) { - this.Url = this.SourceUri.ToString (); - } else { - this.Url = DotNetUrl; - } - } - - internal void AddMethod (MethodInfo mi) - { - if (!this.methods.ContainsKey (mi.Token)) - this.methods [mi.Token] = mi; - } - - public string DebuggerFileName { get; } - public string Url { get; } - public string AssemblyName => assembly.Name; - public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}"; - - public SourceId SourceId => new SourceId (assembly.Id, this.id); - public Uri SourceLinkUri { get; } - public Uri SourceUri { get; } - - public IEnumerable Methods => this.methods.Values; - - public string DocUrl => doc.Url; - - public (int startLine, int startColumn, int endLine, int endColumn) GetExtents () - { - var start = Methods.OrderBy (m => m.StartLocation.Line).ThenBy (m => m.StartLocation.Column).First (); - var end = Methods.OrderByDescending (m => m.EndLocation.Line).ThenByDescending (m => m.EndLocation.Column).First (); - return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column); - } - - async Task GetDataAsync (Uri uri, CancellationToken token) - { - var mem = new MemoryStream (); - try { - if (uri.IsFile && File.Exists (uri.LocalPath)) { - using (var file = File.Open (SourceUri.LocalPath, FileMode.Open)) { - await file.CopyToAsync (mem, token).ConfigureAwait (false); - mem.Position = 0; - } - } else if (uri.Scheme == "http" || uri.Scheme == "https") { - var client = new HttpClient (); - using (var stream = await client.GetStreamAsync (uri)) { - await stream.CopyToAsync (mem, token).ConfigureAwait (false); - mem.Position = 0; - } - } - } catch (Exception) { - return null; - } - return mem; - } - - static HashAlgorithm GetHashAlgorithm (DocumentHashAlgorithm algorithm) - { - switch (algorithm) { - case DocumentHashAlgorithm.SHA1: return SHA1.Create (); - case DocumentHashAlgorithm.SHA256: return SHA256.Create (); - case DocumentHashAlgorithm.MD5: return MD5.Create (); - } - return null; - } - - bool CheckPdbHash (byte [] computedHash) - { - if (computedHash.Length != doc.Hash.Length) - return false; - - for (var i = 0; i < computedHash.Length; i++) - if (computedHash[i] != doc.Hash[i]) - return false; - - return true; - } - - byte[] ComputePdbHash (Stream sourceStream) - { - var algorithm = GetHashAlgorithm (doc.HashAlgorithm); - if (algorithm != null) - using (algorithm) - return algorithm.ComputeHash (sourceStream); - - return Array.Empty (); - } - - public async Task GetSourceAsync (bool checkHash, CancellationToken token = default(CancellationToken)) - { - if (doc.EmbeddedSource.Length > 0) - return new MemoryStream (doc.EmbeddedSource, false); - - foreach (var url in new [] { SourceUri, SourceLinkUri }) { - var mem = await GetDataAsync (url, token).ConfigureAwait (false); - if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) { - mem.Position = 0; - return mem; - } - } - - return MemoryStream.Null; - } - - public object ToScriptSource (int executionContextId, object executionContextAuxData) - { - return new { - scriptId = SourceId.ToString (), - url = Url, - executionContextId, - executionContextAuxData, - //hash: should be the v8 hash algo, managed implementation is pending - dotNetUrl = DotNetUrl, - }; - } - } - - internal class DebugStore { - List assemblies = new List (); - readonly HttpClient client; - readonly ILogger logger; - - public DebugStore (ILogger logger, HttpClient client) { - this.client = client; - this.logger = logger; - } - - public DebugStore (ILogger logger) : this (logger, new HttpClient ()) - { - } - - class DebugItem { - public string Url { get; set; } - public Task Data { get; set; } - } - - public async IAsyncEnumerable Load (SessionId sessionId, string [] loaded_files, [EnumeratorCancellation] CancellationToken token) - { - static bool MatchPdb (string asm, string pdb) - => Path.ChangeExtension (asm, "pdb") == pdb; - - var asm_files = new List (); - var pdb_files = new List (); - foreach (var file_name in loaded_files) { - if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase)) - pdb_files.Add (file_name); - else - asm_files.Add (file_name); - } - - List steps = new List (); - foreach (var url in asm_files) { - try { - var pdb = pdb_files.FirstOrDefault (n => MatchPdb (url, n)); - steps.Add ( - new DebugItem { - Url = url, - Data = Task.WhenAll (client.GetByteArrayAsync (url), pdb != null ? client.GetByteArrayAsync (pdb) : Task.FromResult (null)) - }); - } catch (Exception e) { - logger.LogDebug ($"Failed to read {url} ({e.Message})"); - } - } - - var resolver = new DefaultAssemblyResolver (); - foreach (var step in steps) { - AssemblyInfo assembly = null; - try { - var bytes = await step.Data.ConfigureAwait (false); - assembly = new AssemblyInfo (resolver, step.Url, bytes [0], bytes [1]); - } catch (Exception e) { - logger.LogDebug ($"Failed to load {step.Url} ({e.Message})"); - } - if (assembly == null) - continue; - - assemblies.Add (assembly); - foreach (var source in assembly.Sources) - yield return source; - } - } - - public IEnumerable AllSources () - => assemblies.SelectMany (a => a.Sources); - - public SourceFile GetFileById (SourceId id) - => AllSources ().SingleOrDefault (f => f.SourceId.Equals (id)); - - public AssemblyInfo GetAssemblyByName (string name) - => assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)); - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end) - { - var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); - var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); - - if (start.Line > spEnd.Line) - return false; - - if (start.Column > spEnd.Column && start.Line == spEnd.Line) - return false; - - if (end.Line < spStart.Line) - return false; - - if (end.Column < spStart.Column && end.Line == spStart.Line) - return false; - - return true; - } - - public List FindPossibleBreakpoints (SourceLocation start, SourceLocation end) - { - //XXX FIXME no idea what todo with locations on different files - if (start.Id != end.Id) { - logger.LogDebug ($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}"); - return null; - } - - var sourceId = start.Id; - - var doc = GetFileById (sourceId); - - var res = new List (); - if (doc == null) { - logger.LogDebug ($"Could not find document {sourceId}"); - return res; - } - - foreach (var method in doc.Methods) { - foreach (var sequencePoint in method.DebugInformation.SequencePoints) { - if (!sequencePoint.IsHidden && Match (sequencePoint, start, end)) - res.Add (new SourceLocation (method, sequencePoint)); - } - } - return res; - } - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - static bool Match (SequencePoint sp, int line, int column) - { - var bp = (line: line + 1, column: column + 1); - - if (sp.StartLine > bp.line || sp.EndLine < bp.line) - return false; - - //Chrome sends a zero column even if getPossibleBreakpoints say something else - if (column == 0) - return true; - - if (sp.StartColumn > bp.column && sp.StartLine == bp.line) - return false; - - if (sp.EndColumn < bp.column && sp.EndLine == bp.line) - return false; - - return true; - } - - public IEnumerable FindBreakpointLocations (BreakpointRequest request) - { - request.TryResolve (this); - - var asm = assemblies.FirstOrDefault (a => a.Name.Equals (request.Assembly, StringComparison.OrdinalIgnoreCase)); - var sourceFile = asm?.Sources?.SingleOrDefault (s => s.DebuggerFileName.Equals (request.File, StringComparison.OrdinalIgnoreCase)); - - if (sourceFile == null) - yield break; - - foreach (var method in sourceFile.Methods) { - foreach (var sequencePoint in method.DebugInformation.SequencePoints) { - if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column)) - yield return new SourceLocation (method, sequencePoint); - } - } - } - - public string ToUrl (SourceLocation location) - => location != null ? GetFileById (location.Id).Url : ""; - } -} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebuggerProxy.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebuggerProxy.cs deleted file mode 100644 index 74566c560245..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebuggerProxy.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace WebAssembly.Net.Debugging { - - // This type is the public entrypoint that allows external code to attach the debugger proxy - // to a given websocket listener. Everything else in this package can be internal. - - public class DebuggerProxy { - private readonly MonoProxy proxy; - - public DebuggerProxy (ILoggerFactory loggerFactory) { - proxy = new MonoProxy(loggerFactory); - } - - public Task Run (Uri browserUri, WebSocket ideSocket) { - return proxy.Run (browserUri, ideSocket); - } - } -} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs deleted file mode 100644 index a615540e4502..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using System.Threading; -using System.IO; -using System.Collections.Generic; -using System.Net; -using Microsoft.Extensions.Logging; - -namespace WebAssembly.Net.Debugging { - - internal struct SessionId { - public readonly string sessionId; - - public SessionId (string sessionId) - { - this.sessionId = sessionId; - } - - // hashset treats 0 as unset - public override int GetHashCode () - => sessionId?.GetHashCode () ?? -1; - - public override bool Equals (object obj) - => (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false; - - public static bool operator == (SessionId a, SessionId b) - => a.sessionId == b.sessionId; - - public static bool operator != (SessionId a, SessionId b) - => a.sessionId != b.sessionId; - - public static SessionId Null { get; } = new SessionId (); - - public override string ToString () - => $"session-{sessionId}"; - } - - internal struct MessageId { - public readonly string sessionId; - public readonly int id; - - public MessageId (string sessionId, int id) - { - this.sessionId = sessionId; - this.id = id; - } - - public static implicit operator SessionId (MessageId id) - => new SessionId (id.sessionId); - - public override string ToString () - => $"msg-{sessionId}:::{id}"; - - public override int GetHashCode () - => (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode (); - - public override bool Equals (object obj) - => (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false; - } - - internal class DotnetObjectId { - public string Scheme { get; } - public string Value { get; } - - public static bool TryParse (JToken jToken, out DotnetObjectId objectId) - => TryParse (jToken?.Value(), out objectId); - - public static bool TryParse (string id, out DotnetObjectId objectId) - { - objectId = null; - if (id == null) - return false; - - if (!id.StartsWith ("dotnet:")) - return false; - - var parts = id.Split (":", 3); - - if (parts.Length < 3) - return false; - - objectId = new DotnetObjectId (parts[1], parts[2]); - - return true; - } - - public DotnetObjectId (string scheme, string value) - { - Scheme = scheme; - Value = value; - } - - public override string ToString () - => $"dotnet:{Scheme}:{Value}"; - } - - internal struct Result { - public JObject Value { get; private set; } - public JObject Error { get; private set; } - - public bool IsOk => Value != null; - public bool IsErr => Error != null; - - Result (JObject result, JObject error) - { - if (result != null && error != null) - throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null."); - - bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value (), "error") == 0; - if (result != null && resultHasError) { - this.Value = null; - this.Error = result; - } else { - this.Value = result; - this.Error = error; - } - } - - public static Result FromJson (JObject obj) - { - //Log ("protocol", $"from result: {obj}"); - return new Result (obj ["result"] as JObject, obj ["error"] as JObject); - } - - public static Result Ok (JObject ok) - => new Result (ok, null); - - public static Result OkFromObject (object ok) - => Ok (JObject.FromObject(ok)); - - public static Result Err (JObject err) - => new Result (null, err); - - public static Result Err (string msg) - => new Result (null, JObject.FromObject (new { message = msg })); - - public static Result Exception (Exception e) - => new Result (null, JObject.FromObject (new { message = e.Message })); - - public JObject ToJObject (MessageId target) { - if (IsOk) { - return JObject.FromObject (new { - target.id, - target.sessionId, - result = Value - }); - } else { - return JObject.FromObject (new { - target.id, - target.sessionId, - error = Error - }); - } - } - - public override string ToString () - { - return $"[Result: IsOk: {IsOk}, IsErr: {IsErr}, Value: {Value?.ToString ()}, Error: {Error?.ToString ()} ]"; - } - } - - internal class MonoCommands { - public string expression { get; set; } - public string objectGroup { get; set; } = "mono-debugger"; - public bool includeCommandLineAPI { get; set; } = false; - public bool silent { get; set; } = false; - public bool returnByValue { get; set; } = true; - - public MonoCommands (string expression) - => this.expression = expression; - - public static MonoCommands GetCallStack () - => new MonoCommands ("MONO.mono_wasm_get_call_stack()"); - - public static MonoCommands IsRuntimeReady () - => new MonoCommands ("MONO.mono_wasm_runtime_is_ready"); - - public static MonoCommands StartSingleStepping (StepKind kind) - => new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})"); - - public static MonoCommands GetLoadedFiles () - => new MonoCommands ("MONO.mono_wasm_get_loaded_files()"); - - public static MonoCommands ClearAllBreakpoints () - => new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()"); - - public static MonoCommands GetDetails (DotnetObjectId objectId, JToken args = null) - => new MonoCommands ($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{}")})"); - - public static MonoCommands GetScopeVariables (int scopeId, params VarInfo[] vars) - { - var var_ids = vars.Select (v => new { index = v.Index, name = v.Name }).ToArray (); - return new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject (var_ids)})"); - } - - public static MonoCommands SetBreakpoint (string assemblyName, uint methodToken, int ilOffset) - => new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})"); - - public static MonoCommands RemoveBreakpoint (int breakpointId) - => new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})"); - - public static MonoCommands ReleaseObject (DotnetObjectId objectId) - => new MonoCommands ($"MONO.mono_wasm_release_object('{objectId}')"); - - public static MonoCommands CallFunctionOn (JToken args) - => new MonoCommands ($"MONO.mono_wasm_call_function_on ({args.ToString ()})"); - - public static MonoCommands Resume () - => new MonoCommands ($"MONO.mono_wasm_debugger_resume ()"); - } - - internal enum MonoErrorCodes { - BpNotFound = 100000, - } - - internal class MonoConstants { - public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready"; - } - - class Frame { - public Frame (MethodInfo method, SourceLocation location, int id) - { - this.Method = method; - this.Location = location; - this.Id = id; - } - - public MethodInfo Method { get; private set; } - public SourceLocation Location { get; private set; } - public int Id { get; private set; } - } - - class Breakpoint { - public SourceLocation Location { get; private set; } - public int RemoteId { get; set; } - public BreakpointState State { get; set; } - public string StackId { get; private set; } - - public static bool TryParseId (string stackId, out int id) - { - id = -1; - if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true) - return false; - - return int.TryParse (stackId.Substring ("dotnet:".Length), out id); - } - - public Breakpoint (string stackId, SourceLocation loc, BreakpointState state) - { - this.StackId = stackId; - this.Location = loc; - this.State = state; - } - } - - enum BreakpointState { - Active, - Disabled, - Pending - } - - enum StepKind { - Into, - Out, - Over - } - - internal class ExecutionContext { - public string DebuggerId { get; set; } - public Dictionary BreakpointRequests { get; } = new Dictionary (); - - public TaskCompletionSource ready = null; - public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; - - public int Id { get; set; } - public object AuxData { get; set; } - - public List CallStack { get; set; } - - public string[] LoadedFiles { get; set; } - internal DebugStore store; - public TaskCompletionSource Source { get; } = new TaskCompletionSource (); - - public Dictionary LocalsCache = new Dictionary (); - - public DebugStore Store { - get { - if (store == null || !Source.Task.IsCompleted) - return null; - - return store; - } - } - - public void ClearState () - { - CallStack = null; - LocalsCache.Clear (); - } - - } -} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsProxy.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsProxy.cs deleted file mode 100644 index 5eac86d124a5..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsProxy.cs +++ /dev/null @@ -1,337 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using System.Net.WebSockets; -using System.Threading; -using System.IO; -using System.Text; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace WebAssembly.Net.Debugging { - - class DevToolsQueue { - Task current_send; - List pending; - - public WebSocket Ws { get; private set; } - public Task CurrentSend { get { return current_send; } } - public DevToolsQueue (WebSocket sock) - { - this.Ws = sock; - pending = new List (); - } - - public Task Send (byte [] bytes, CancellationToken token) - { - pending.Add (bytes); - if (pending.Count == 1) { - if (current_send != null) - throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send"); - //logger.LogTrace ("sending {0} bytes", bytes.Length); - current_send = Ws.SendAsync (new ArraySegment (bytes), WebSocketMessageType.Text, true, token); - return current_send; - } - return null; - } - - public Task Pump (CancellationToken token) - { - current_send = null; - pending.RemoveAt (0); - - if (pending.Count > 0) { - if (current_send != null) - throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send"); - - current_send = Ws.SendAsync (new ArraySegment (pending [0]), WebSocketMessageType.Text, true, token); - return current_send; - } - return null; - } - } - - internal class DevToolsProxy { - TaskCompletionSource side_exception = new TaskCompletionSource (); - TaskCompletionSource client_initiated_close = new TaskCompletionSource (); - Dictionary> pending_cmds = new Dictionary> (); - ClientWebSocket browser; - WebSocket ide; - int next_cmd_id; - List pending_ops = new List (); - List queues = new List (); - - protected readonly ILogger logger; - - public DevToolsProxy (ILoggerFactory loggerFactory) - { - logger = loggerFactory.CreateLogger(); - } - - protected virtual Task AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token) - { - return Task.FromResult (false); - } - - protected virtual Task AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) - { - return Task.FromResult (false); - } - - async Task ReadOne (WebSocket socket, CancellationToken token) - { - byte [] buff = new byte [4000]; - var mem = new MemoryStream (); - while (true) { - - if (socket.State != WebSocketState.Open) { - Log ("error", $"DevToolsProxy: Socket is no longer open."); - client_initiated_close.TrySetResult (true); - return null; - } - - var result = await socket.ReceiveAsync (new ArraySegment (buff), token); - if (result.MessageType == WebSocketMessageType.Close) { - client_initiated_close.TrySetResult (true); - return null; - } - - mem.Write (buff, 0, result.Count); - - if (result.EndOfMessage) - return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length); - } - } - - DevToolsQueue GetQueueForSocket (WebSocket ws) - { - return queues.FirstOrDefault (q => q.Ws == ws); - } - - DevToolsQueue GetQueueForTask (Task task) - { - return queues.FirstOrDefault (q => q.CurrentSend == task); - } - - void Send (WebSocket to, JObject o, CancellationToken token) - { - var sender = browser == to ? "Send-browser" : "Send-ide"; - - var method = o ["method"]?.ToString (); - //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") - Log ("protocol", $"{sender}: " + JsonConvert.SerializeObject (o)); - var bytes = Encoding.UTF8.GetBytes (o.ToString ()); - - var queue = GetQueueForSocket (to); - - var task = queue.Send (bytes, token); - if (task != null) - pending_ops.Add (task); - } - - async Task OnEvent (SessionId sessionId, string method, JObject args, CancellationToken token) - { - try { - if (!await AcceptEvent (sessionId, method, args, token)) { - //logger.LogDebug ("proxy browser: {0}::{1}",method, args); - SendEventInternal (sessionId, method, args, token); - } - } catch (Exception e) { - side_exception.TrySetException (e); - } - } - - async Task OnCommand (MessageId id, string method, JObject args, CancellationToken token) - { - try { - if (!await AcceptCommand (id, method, args, token)) { - var res = await SendCommandInternal (id, method, args, token); - SendResponseInternal (id, res, token); - } - } catch (Exception e) { - side_exception.TrySetException (e); - } - } - - void OnResponse (MessageId id, Result result) - { - //logger.LogTrace ("got id {0} res {1}", id, result); - // Fixme - if (pending_cmds.Remove (id, out var task)) { - task.SetResult (result); - return; - } - logger.LogError ("Cannot respond to command: {id} with result: {result} - command is not pending", id, result); - } - - void ProcessBrowserMessage (string msg, CancellationToken token) - { - var res = JObject.Parse (msg); - - var method = res ["method"]?.ToString (); - //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") - Log ("protocol", $"browser: {msg}"); - - if (res ["id"] == null) - pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value ()), res ["method"].Value (), res ["params"] as JObject, token)); - else - OnResponse (new MessageId (res ["sessionId"]?.Value (), res ["id"].Value ()), Result.FromJson (res)); - } - - void ProcessIdeMessage (string msg, CancellationToken token) - { - Log ("protocol", $"ide: {msg}"); - if (!string.IsNullOrEmpty (msg)) { - var res = JObject.Parse (msg); - pending_ops.Add (OnCommand ( - new MessageId (res ["sessionId"]?.Value (), res ["id"].Value ()), - res ["method"].Value (), - res ["params"] as JObject, token)); - } - } - - internal async Task SendCommand (SessionId id, string method, JObject args, CancellationToken token) { - //Log ("verbose", $"sending command {method}: {args}"); - return await SendCommandInternal (id, method, args, token); - } - - Task SendCommandInternal (SessionId sessionId, string method, JObject args, CancellationToken token) - { - int id = Interlocked.Increment (ref next_cmd_id); - - var o = JObject.FromObject (new { - id, - method, - @params = args - }); - if (sessionId.sessionId != null) - o["sessionId"] = sessionId.sessionId; - var tcs = new TaskCompletionSource (); - - var msgId = new MessageId (sessionId.sessionId, id); - //Log ("verbose", $"add cmd id {sessionId}-{id}"); - pending_cmds[msgId] = tcs; - - Send (this.browser, o, token); - return tcs.Task; - } - - public void SendEvent (SessionId sessionId, string method, JObject args, CancellationToken token) - { - //Log ("verbose", $"sending event {method}: {args}"); - SendEventInternal (sessionId, method, args, token); - } - - void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token) - { - var o = JObject.FromObject (new { - method, - @params = args - }); - if (sessionId.sessionId != null) - o["sessionId"] = sessionId.sessionId; - - Send (this.ide, o, token); - } - - internal void SendResponse (MessageId id, Result result, CancellationToken token) - { - SendResponseInternal (id, result, token); - } - - void SendResponseInternal (MessageId id, Result result, CancellationToken token) - { - JObject o = result.ToJObject (id); - if (result.IsErr) - logger.LogError ($"sending error response for id: {id} -> {result}"); - - Send (this.ide, o, token); - } - - // , HttpContext context) - public async Task Run (Uri browserUri, WebSocket ideSocket) - { - Log ("info", $"DevToolsProxy: Starting on {browserUri}"); - using (this.ide = ideSocket) { - Log ("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}"); - queues.Add (new DevToolsQueue (this.ide)); - using (this.browser = new ClientWebSocket ()) { - this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; - await this.browser.ConnectAsync (browserUri, CancellationToken.None); - queues.Add (new DevToolsQueue (this.browser)); - - Log ("verbose", $"DevToolsProxy: Client connected on {browserUri}"); - var x = new CancellationTokenSource (); - - pending_ops.Add (ReadOne (browser, x.Token)); - pending_ops.Add (ReadOne (ide, x.Token)); - pending_ops.Add (side_exception.Task); - pending_ops.Add (client_initiated_close.Task); - - try { - while (!x.IsCancellationRequested) { - var task = await Task.WhenAny (pending_ops.ToArray ()); - //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task)); - if (task == pending_ops [0]) { - var msg = ((Task)task).Result; - if (msg != null) { - pending_ops [0] = ReadOne (browser, x.Token); //queue next read - ProcessBrowserMessage (msg, x.Token); - } - } else if (task == pending_ops [1]) { - var msg = ((Task)task).Result; - if (msg != null) { - pending_ops [1] = ReadOne (ide, x.Token); //queue next read - ProcessIdeMessage (msg, x.Token); - } - } else if (task == pending_ops [2]) { - var res = ((Task)task).Result; - throw new Exception ("side task must always complete with an exception, what's going on???"); - } else if (task == pending_ops [3]) { - var res = ((Task)task).Result; - Log ("verbose", $"DevToolsProxy: Client initiated close from {browserUri}"); - x.Cancel (); - } else { - //must be a background task - pending_ops.Remove (task); - var queue = GetQueueForTask (task); - if (queue != null) { - var tsk = queue.Pump (x.Token); - if (tsk != null) - pending_ops.Add (tsk); - } - } - } - } catch (Exception e) { - Log ("error", $"DevToolsProxy::Run: Exception {e}"); - //throw; - } finally { - if (!x.IsCancellationRequested) - x.Cancel (); - } - } - } - } - - protected void Log (string priority, string msg) - { - switch (priority) { - case "protocol": - logger.LogTrace (msg); - break; - case "verbose": - logger.LogDebug (msg); - break; - case "info": - case "warning": - case "error": - default: - logger.LogDebug (msg); - break; - } - } - } -} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/EvaluateExpression.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/EvaluateExpression.cs deleted file mode 100644 index 7bcf6f811746..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/EvaluateExpression.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; - -using System.Threading; -using System.IO; -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using System.Reflection; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace WebAssembly.Net.Debugging { - - internal class EvaluateExpression { - - class FindThisExpression : CSharpSyntaxWalker { - public List thisExpressions = new List (); - public SyntaxTree syntaxTree; - public FindThisExpression (SyntaxTree syntax) - { - syntaxTree = syntax; - } - public override void Visit (SyntaxNode node) - { - if (node is ThisExpressionSyntax) { - if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) { - IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax; - thisExpressions.Add(var.Identifier.Text); - var newRoot = syntaxTree.GetRoot ().ReplaceNode (node.Parent, thisParent.Name); - syntaxTree = syntaxTree.WithRootAndOptions (newRoot, syntaxTree.Options); - this.Visit (GetExpressionFromSyntaxTree(syntaxTree)); - } - } - else - base.Visit (node); - } - - public async Task CheckIfIsProperty (MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) - { - foreach (var var in thisExpressions) { - JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var, true, token); - if (value == null) - throw new Exception ($"The property {var} does not exist in the current context"); - } - } - } - - class FindVariableNMethodCall : CSharpSyntaxWalker { - public List variables = new List (); - public List thisList = new List (); - public List methodCall = new List (); - public List values = new List (); - - public override void Visit (SyntaxNode node) - { - if (node is IdentifierNameSyntax identifier && !variables.Any (x => x.Identifier.Text == identifier.Identifier.Text)) - variables.Add (identifier); - if (node is InvocationExpressionSyntax) { - methodCall.Add (node as InvocationExpressionSyntax); - throw new Exception ("Method Call is not implemented yet"); - } - if (node is AssignmentExpressionSyntax) - throw new Exception ("Assignment is not implemented yet"); - base.Visit (node); - } - public async Task ReplaceVars (SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) - { - CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot (); - foreach (var var in variables) { - ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax; - MethodDeclarationSyntax method = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax; - - JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var.Identifier.Text, false, token); - - if (value == null) - throw new Exception ($"The name {var.Identifier.Text} does not exist in the current context"); - - values.Add (ConvertJSToCSharpType (value ["value"])); - - var updatedMethod = method.AddParameterListParameters ( - SyntaxFactory.Parameter ( - SyntaxFactory.Identifier (var.Identifier.Text)) - .WithType (SyntaxFactory.ParseTypeName (GetTypeFullName(value["value"])))); - root = root.ReplaceNode (method, updatedMethod); - } - syntaxTree = syntaxTree.WithRootAndOptions (root, syntaxTree.Options); - return syntaxTree; - } - - private object ConvertJSToCSharpType (JToken variable) - { - var value = variable["value"]; - var type = variable["type"].Value(); - var subType = variable["subtype"]?.Value(); - - switch (type) { - case "string": - return value?.Value (); - case "number": - return value?.Value (); - case "boolean": - return value?.Value (); - case "object": - if (subType == "null") - return null; - break; - } - throw new Exception ($"Evaluate of this datatype {type} not implemented yet"); - } - - private string GetTypeFullName (JToken variable) - { - var type = variable["type"].ToString (); - var subType = variable["subtype"]?.Value(); - object value = ConvertJSToCSharpType (variable); - - switch (type) { - case "object": { - if (subType == "null") - return variable["className"].Value(); - break; - } - default: - return value.GetType ().FullName; - } - throw new Exception ($"Evaluate of this datatype {type} not implemented yet"); - } - } - - static SyntaxNode GetExpressionFromSyntaxTree (SyntaxTree syntaxTree) - { - CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot (); - ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax; - MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax; - BlockSyntax blockValue = methodDeclaration.Body; - ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt (0) as ReturnStatementSyntax; - InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax; - MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax; - ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax; - return expressionParenthesized.Expression; - } - - internal static async Task CompileAndRunTheExpression (MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token) - { - FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall (); - string retString; - SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText (@" - using System; - public class CompileAndRunTheExpression - { - public string Evaluate() - { - return (" + expression + @").ToString(); - } - }"); - - FindThisExpression findThisExpression = new FindThisExpression (syntaxTree); - var expressionTree = GetExpressionFromSyntaxTree(syntaxTree); - findThisExpression.Visit (expressionTree); - await findThisExpression.CheckIfIsProperty (proxy, msg_id, scope_id, token); - syntaxTree = findThisExpression.syntaxTree; - - expressionTree = GetExpressionFromSyntaxTree (syntaxTree); - findVarNMethodCall.Visit (expressionTree); - - syntaxTree = await findVarNMethodCall.ReplaceVars (syntaxTree, proxy, msg_id, scope_id, token); - - MetadataReference [] references = new MetadataReference [] - { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) - }; - - CSharpCompilation compilation = CSharpCompilation.Create ( - "compileAndRunTheExpression", - syntaxTrees: new [] { syntaxTree }, - references: references, - options: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary)); - using (var ms = new MemoryStream ()) { - EmitResult result = compilation.Emit (ms); - ms.Seek (0, SeekOrigin.Begin); - Assembly assembly = Assembly.Load (ms.ToArray ()); - Type type = assembly.GetType ("CompileAndRunTheExpression"); - object obj = Activator.CreateInstance (type); - var ret = type.InvokeMember ("Evaluate", - BindingFlags.Default | BindingFlags.InvokeMethod, - null, - obj, - findVarNMethodCall.values.ToArray ()); - retString = ret.ToString (); - } - return retString; - } - } -} diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs deleted file mode 100644 index 5169d1ccfff3..000000000000 --- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs +++ /dev/null @@ -1,881 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; - -using System.Threading; -using System.IO; -using System.Collections.Generic; -using System.Net; -using Microsoft.Extensions.Logging; -using Microsoft.CodeAnalysis; - - -namespace WebAssembly.Net.Debugging { - - internal class MonoProxy : DevToolsProxy { - HashSet sessions = new HashSet (); - Dictionary contexts = new Dictionary (); - - public MonoProxy (ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { hideWebDriver = true; } - - readonly bool hideWebDriver; - - internal ExecutionContext GetContext (SessionId sessionId) - { - if (contexts.TryGetValue (sessionId, out var context)) - return context; - - throw new ArgumentException ($"Invalid Session: \"{sessionId}\"", nameof (sessionId)); - } - - bool UpdateContext (SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext) - { - var previous = contexts.TryGetValue (sessionId, out previousExecutionContext); - contexts[sessionId] = executionContext; - return previous; - } - - internal Task SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token) - => SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token); - - protected override async Task AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token) - { - switch (method) { - case "Runtime.consoleAPICalled": { - var type = args["type"]?.ToString (); - if (type == "debug") { - var a = args ["args"]; - if (a? [0]? ["value"]?.ToString () == MonoConstants.RUNTIME_IS_READY && - a? [1]? ["value"]?.ToString () == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28") { - if (a.Count () > 2) { - try { - // The optional 3rd argument is the stringified assembly - // list so that we don't have to make more round trips - var context = GetContext (sessionId); - var loaded = a? [2]? ["value"]?.ToString (); - if (loaded != null) - context.LoadedFiles = JToken.Parse (loaded).ToObject (); - } catch (InvalidCastException ice) { - Log ("verbose", ice.ToString ()); - } - } - await RuntimeReady (sessionId, token); - } - - } - break; - } - - case "Runtime.executionContextCreated": { - SendEvent (sessionId, method, args, token); - var ctx = args? ["context"]; - var aux_data = ctx? ["auxData"] as JObject; - var id = ctx ["id"].Value (); - if (aux_data != null) { - var is_default = aux_data ["isDefault"]?.Value (); - if (is_default == true) { - await OnDefaultContext (sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token); - } - } - return true; - } - - case "Debugger.paused": { - //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack - var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value (); - - if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") { - return await OnBreakpointHit (sessionId, args, token); - } - break; - } - - case "Debugger.breakpointResolved": { - break; - } - - case "Debugger.scriptParsed": { - var url = args? ["url"]?.Value () ?? ""; - - switch (url) { - case var _ when url == "": - case var _ when url.StartsWith ("wasm://", StringComparison.Ordinal): { - Log ("verbose", $"ignoring wasm: Debugger.scriptParsed {url}"); - return true; - } - } - Log ("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}"); - break; - } - - case "Target.attachedToTarget": { - if (args["targetInfo"]["type"]?.ToString() == "page") - await DeleteWebDriver (new SessionId (args["sessionId"]?.ToString ()), token); - break; - } - - } - - return false; - } - - async Task IsRuntimeAlreadyReadyAlready (SessionId sessionId, CancellationToken token) - { - if (contexts.TryGetValue (sessionId, out var context) && context.IsRuntimeReady) - return true; - - var res = await SendMonoCommand (sessionId, MonoCommands.IsRuntimeReady (), token); - return res.Value? ["result"]? ["value"]?.Value () ?? false; - } - - static int bpIdGenerator; - - protected override async Task AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) - { - // Inspector doesn't use the Target domain or sessions - // so we try to init immediately - if (hideWebDriver && id == SessionId.Null) - await DeleteWebDriver (id, token); - - if (!contexts.TryGetValue (id, out var context)) - return false; - - switch (method) { - case "Target.attachToTarget": { - var resp = await SendCommand (id, method, args, token); - await DeleteWebDriver (new SessionId (resp.Value ["sessionId"]?.ToString ()), token); - break; - } - - case "Debugger.enable": { - var resp = await SendCommand (id, method, args, token); - - context.DebuggerId = resp.Value ["debuggerId"]?.ToString (); - - if (await IsRuntimeAlreadyReadyAlready (id, token)) - await RuntimeReady (id, token); - - SendResponse (id,resp,token); - return true; - } - - case "Debugger.getScriptSource": { - var script = args? ["scriptId"]?.Value (); - return await OnGetScriptSource (id, script, token); - } - - case "Runtime.compileScript": { - var exp = args? ["expression"]?.Value (); - if (exp.StartsWith ("//dotnet:", StringComparison.Ordinal)) { - OnCompileDotnetScript (id, token); - return true; - } - break; - } - - case "Debugger.getPossibleBreakpoints": { - var resp = await SendCommand (id, method, args, token); - if (resp.IsOk && resp.Value["locations"].HasValues) { - SendResponse (id, resp, token); - return true; - } - - var start = SourceLocation.Parse (args? ["start"] as JObject); - //FIXME support variant where restrictToFunction=true and end is omitted - var end = SourceLocation.Parse (args? ["end"] as JObject); - if (start != null && end != null && await GetPossibleBreakpoints (id, start, end, token)) - return true; - - SendResponse (id, resp, token); - return true; - } - - case "Debugger.setBreakpoint": { - break; - } - - case "Debugger.setBreakpointByUrl": { - var resp = await SendCommand (id, method, args, token); - if (!resp.IsOk) { - SendResponse (id, resp, token); - return true; - } - - var bpid = resp.Value["breakpointId"]?.ToString (); - var locations = resp.Value["locations"]?.Values(); - var request = BreakpointRequest.Parse (bpid, args); - - // is the store done loading? - var loaded = context.Source.Task.IsCompleted; - if (!loaded) { - // Send and empty response immediately if not - // and register the breakpoint for resolution - context.BreakpointRequests [bpid] = request; - SendResponse (id, resp, token); - } - - if (await IsRuntimeAlreadyReadyAlready (id, token)) { - var store = await RuntimeReady (id, token); - - Log ("verbose", $"BP req {args}"); - await SetBreakpoint (id, store, request, !loaded, token); - } - - if (loaded) { - // we were already loaded so we should send a response - // with the locations included and register the request - context.BreakpointRequests [bpid] = request; - var result = Result.OkFromObject (request.AsSetBreakpointByUrlResponse (locations)); - SendResponse (id, result, token); - - } - return true; - } - - case "Debugger.removeBreakpoint": { - await RemoveBreakpoint (id, args, token); - break; - } - - case "Debugger.resume": { - await OnResume (id, token); - break; - } - - case "Debugger.stepInto": { - return await Step (id, StepKind.Into, token); - } - - case "Debugger.stepOut": { - return await Step (id, StepKind.Out, token); - } - - case "Debugger.stepOver": { - return await Step (id, StepKind.Over, token); - } - - case "Debugger.evaluateOnCallFrame": { - if (!DotnetObjectId.TryParse (args? ["callFrameId"], out var objectId)) - return false; - - switch (objectId.Scheme) { - case "scope": - return await OnEvaluateOnCallFrame (id, - int.Parse (objectId.Value), - args? ["expression"]?.Value (), token); - default: - return false; - } - } - - case "Runtime.getProperties": { - if (!DotnetObjectId.TryParse (args? ["objectId"], out var objectId)) - break; - - var result = await RuntimeGetProperties (id, objectId, args, token); - SendResponse (id, result, token); - return true; - } - - case "Runtime.releaseObject": { - if (!(DotnetObjectId.TryParse (args ["objectId"], out var objectId) && objectId.Scheme == "cfo_res")) - break; - - await SendMonoCommand (id, MonoCommands.ReleaseObject (objectId), token); - SendResponse (id, Result.OkFromObject (new{}), token); - return true; - } - - // Protocol extensions - case "DotnetDebugger.getMethodLocation": { - Console.WriteLine ("set-breakpoint-by-method: " + id + " " + args); - - var store = await RuntimeReady (id, token); - string aname = args ["assemblyName"]?.Value (); - string typeName = args ["typeName"]?.Value (); - string methodName = args ["methodName"]?.Value (); - if (aname == null || typeName == null || methodName == null) { - SendResponse (id, Result.Err ("Invalid protocol message '" + args + "'."), token); - return true; - } - - // GetAssemblyByName seems to work on file names - var assembly = store.GetAssemblyByName (aname); - if (assembly == null) - assembly = store.GetAssemblyByName (aname + ".exe"); - if (assembly == null) - assembly = store.GetAssemblyByName (aname + ".dll"); - if (assembly == null) { - SendResponse (id, Result.Err ("Assembly '" + aname + "' not found."), token); - return true; - } - - var type = assembly.GetTypeByName (typeName); - if (type == null) { - SendResponse (id, Result.Err ($"Type '{typeName}' not found."), token); - return true; - } - - var methodInfo = type.Methods.FirstOrDefault (m => m.Name == methodName); - if (methodInfo == null) { - // Maybe this is an async method, in which case the debug info is attached - // to the async method implementation, in class named: - // `{type_name}/::MoveNext` - methodInfo = assembly.TypesByName.Values.SingleOrDefault (t => t.FullName.StartsWith ($"{typeName}/<{methodName}>")) - ?.Methods.FirstOrDefault (mi => mi.Name == "MoveNext"); - } - - if (methodInfo == null) { - SendResponse (id, Result.Err ($"Method '{typeName}:{methodName}' not found."), token); - return true; - } - - var src_url = methodInfo.Assembly.Sources.Single (sf => sf.SourceId == methodInfo.SourceId).Url; - SendResponse (id, Result.OkFromObject (new { - result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url } - }), token); - - return true; - } - case "Runtime.callFunctionOn": { - if (!DotnetObjectId.TryParse (args ["objectId"], out var objectId)) - return false; - - if (objectId.Scheme == "scope") { - SendResponse (id, - Result.Exception (new ArgumentException ( - $"Runtime.callFunctionOn not supported with scope ({objectId}).")), - token); - return true; - } - - var res = await SendMonoCommand (id, MonoCommands.CallFunctionOn (args), token); - var res_value_type = res.Value? ["result"]? ["value"]?.Type; - - if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object) - res = Result.OkFromObject (new { result = res.Value ["result"]["value"] }); - - SendResponse (id, res, token); - return true; - } - } - - return false; - } - - async Task RuntimeGetProperties (MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token) - { - if (objectId.Scheme == "scope") - return await GetScopeProperties (id, int.Parse (objectId.Value), token); - - var res = await SendMonoCommand (id, MonoCommands.GetDetails (objectId, args), token); - if (res.IsErr) - return res; - - if (objectId.Scheme == "cfo_res") { - // Runtime.callFunctionOn result object - var value_json_str = res.Value ["result"]?["value"]?["__value_as_json_string__"]?.Value (); - if (value_json_str != null) { - res = Result.OkFromObject (new { - result = JArray.Parse (value_json_str) - }); - } else { - res = Result.OkFromObject (new { result = new {} }); - } - } else { - res = Result.Ok (JObject.FromObject (new { result = res.Value ["result"] ["value"] })); - } - - return res; - } - - //static int frame_id=0; - async Task OnBreakpointHit (SessionId sessionId, JObject args, CancellationToken token) - { - //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime - var res = await SendMonoCommand (sessionId, MonoCommands.GetCallStack(), token); - var orig_callframes = args? ["callFrames"]?.Values (); - var context = GetContext (sessionId); - - if (res.IsErr) { - //Give up and send the original call stack - return false; - } - - //step one, figure out where did we hit - var res_value = res.Value? ["result"]? ["value"]; - if (res_value == null || res_value is JValue) { - //Give up and send the original call stack - return false; - } - - Log ("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); - var bp_id = res_value? ["breakpoint_id"]?.Value (); - Log ("verbose", $"We just hit bp {bp_id}"); - if (!bp_id.HasValue) { - //Give up and send the original call stack - return false; - } - - var bp = context.BreakpointRequests.Values.SelectMany (v => v.Locations).FirstOrDefault (b => b.RemoteId == bp_id.Value); - - var callFrames = new List (); - foreach (var frame in orig_callframes) { - var function_name = frame ["functionName"]?.Value (); - var url = frame ["url"]?.Value (); - if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { - var frames = new List (); - int frame_id = 0; - var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values (); - - foreach (var mono_frame in the_mono_frames) { - ++frame_id; - var il_pos = mono_frame ["il_pos"].Value (); - var method_token = mono_frame ["method_token"].Value (); - var assembly_name = mono_frame ["assembly_name"].Value (); - - // This can be different than `method.Name`, like in case of generic methods - var method_name = mono_frame ["method_name"]?.Value (); - - var store = await LoadStore (sessionId, token); - var asm = store.GetAssemblyByName (assembly_name); - if (asm == null) { - Log ("info",$"Unable to find assembly: {assembly_name}"); - continue; - } - - var method = asm.GetMethodByToken (method_token); - - if (method == null) { - Log ("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); - continue; - } - - var location = method?.GetLocationByIl (il_pos); - - // When hitting a breakpoint on the "IncrementCount" method in the standard - // Blazor project template, one of the stack frames is inside mscorlib.dll - // and we get location==null for it. It will trigger a NullReferenceException - // if we don't skip over that stack frame. - if (location == null) { - continue; - } - - Log ("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); - Log ("info", $"\tmethod {method_name} location: {location}"); - frames.Add (new Frame (method, location, frame_id-1)); - - callFrames.Add (new { - functionName = method_name, - callFrameId = $"dotnet:scope:{frame_id-1}", - functionLocation = method.StartLocation.AsLocation (), - - location = location.AsLocation (), - - url = store.ToUrl (location), - - scopeChain = new [] { - new { - type = "local", - @object = new { - @type = "object", - className = "Object", - description = "Object", - objectId = $"dotnet:scope:{frame_id-1}", - }, - name = method_name, - startLocation = method.StartLocation.AsLocation (), - endLocation = method.EndLocation.AsLocation (), - }} - }); - - context.CallStack = frames; - - } - } else if (!(function_name.StartsWith ("wasm-function", StringComparison.Ordinal) - || url.StartsWith ("wasm://wasm/", StringComparison.Ordinal))) { - callFrames.Add (frame); - } - } - - var bp_list = new string [bp == null ? 0 : 1]; - if (bp != null) - bp_list [0] = bp.StackId; - - var o = JObject.FromObject (new { - callFrames, - reason = "other", //other means breakpoint - hitBreakpoints = bp_list, - }); - - SendEvent (sessionId, "Debugger.paused", o, token); - return true; - } - - async Task OnDefaultContext (SessionId sessionId, ExecutionContext context, CancellationToken token) - { - Log ("verbose", "Default context created, clearing state and sending events"); - if (UpdateContext (sessionId, context, out var previousContext)) { - foreach (var kvp in previousContext.BreakpointRequests) { - context.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); - } - } - - if (await IsRuntimeAlreadyReadyAlready (sessionId, token)) - await RuntimeReady (sessionId, token); - } - - async Task OnResume (MessageId msg_id, CancellationToken token) - { - var ctx = GetContext (msg_id); - if (ctx.CallStack != null) { - // Stopped on managed code - await SendMonoCommand (msg_id, MonoCommands.Resume (), token); - } - - //discard managed frames - GetContext (msg_id).ClearState (); - } - - async Task Step (MessageId msg_id, StepKind kind, CancellationToken token) - { - var context = GetContext (msg_id); - if (context.CallStack == null) - return false; - - if (context.CallStack.Count <= 1 && kind == StepKind.Out) - return false; - - var res = await SendMonoCommand (msg_id, MonoCommands.StartSingleStepping (kind), token); - - var ret_code = res.Value? ["result"]? ["value"]?.Value (); - - if (ret_code.HasValue && ret_code.Value == 0) { - context.ClearState (); - await SendCommand (msg_id, "Debugger.stepOut", new JObject (), token); - return false; - } - - SendResponse (msg_id, Result.Ok (new JObject ()), token); - - context.ClearState (); - - await SendCommand (msg_id, "Debugger.resume", new JObject (), token); - return true; - } - - internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj) - { - if (ctx.LocalsCache.TryGetValue (expression, out obj)) { - if (only_search_on_this && obj["fromThis"] == null) - return false; - return true; - } - return false; - } - - internal async Task TryGetVariableValue (MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token) - { - JToken thisValue = null; - var context = GetContext (msg_id); - if (context.CallStack == null) - return null; - - if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj)) - return obj; - - var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id); - var live_vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset); - //get_this - var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, live_vars), token); - - var scope_values = res.Value? ["result"]? ["value"]?.Values ()?.ToArray (); - thisValue = scope_values?.FirstOrDefault (v => v ["name"]?.Value () == "this"); - - if (!only_search_on_this) { - if (thisValue != null && expression == "this") - return thisValue; - - var value = scope_values.SingleOrDefault (sv => sv ["name"]?.Value () == expression); - if (value != null) - return value; - } - - //search in scope - if (thisValue != null) { - if (!DotnetObjectId.TryParse (thisValue ["value"] ["objectId"], out var objectId)) - return null; - - res = await SendMonoCommand (msg_id, MonoCommands.GetDetails (objectId), token); - scope_values = res.Value? ["result"]? ["value"]?.Values ().ToArray (); - var foundValue = scope_values.FirstOrDefault (v => v ["name"].Value () == expression); - if (foundValue != null) { - foundValue["fromThis"] = true; - context.LocalsCache[foundValue ["name"].Value ()] = foundValue; - return foundValue; - } - } - return null; - } - - async Task OnEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token) - { - try { - var context = GetContext (msg_id); - if (context.CallStack == null) - return false; - - var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token); - - if (varValue != null) { - SendResponse (msg_id, Result.OkFromObject (new { - result = varValue ["value"] - }), token); - return true; - } - - string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token); - SendResponse (msg_id, Result.OkFromObject (new { - result = new { - value = retValue - } - }), token); - return true; - } catch (Exception e) { - logger.LogDebug (e, $"Error in EvaluateOnCallFrame for expression '{expression}."); - } - return false; - } - - async Task GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token) - { - try { - var ctx = GetContext (msg_id); - var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id); - if (scope == null) - return Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" })); - - var var_ids = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset); - var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (res.IsErr) - return res; - - var values = res.Value? ["result"]? ["value"]?.Values ().ToArray (); - - if(values == null || values.Length == 0) - return Result.OkFromObject (new { result = Array.Empty () }); - - foreach (var value in values) - ctx.LocalsCache [value ["name"]?.Value ()] = value; - - return Result.OkFromObject (new { result = values }); - } catch (Exception exception) { - Log ("verbose", $"Error resolving scope properties {exception.Message}"); - return Result.Exception (exception); - } - } - - async Task SetMonoBreakpoint (SessionId sessionId, string reqId, SourceLocation location, CancellationToken token) - { - var bp = new Breakpoint (reqId, location, BreakpointState.Pending); - var asm_name = bp.Location.CliLocation.Method.Assembly.Name; - var method_token = bp.Location.CliLocation.Method.Token; - var il_offset = bp.Location.CliLocation.Offset; - - var res = await SendMonoCommand (sessionId, MonoCommands.SetBreakpoint (asm_name, method_token, il_offset), token); - var ret_code = res.Value? ["result"]? ["value"]?.Value (); - - if (ret_code.HasValue) { - bp.RemoteId = ret_code.Value; - bp.State = BreakpointState.Active; - //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); - } - - return bp; - } - - async Task LoadStore (SessionId sessionId, CancellationToken token) - { - var context = GetContext (sessionId); - - if (Interlocked.CompareExchange (ref context.store, new DebugStore (logger), null) != null) - return await context.Source.Task; - - try { - var loaded_files = context.LoadedFiles; - - if (loaded_files == null) { - var loaded = await SendMonoCommand (sessionId, MonoCommands.GetLoadedFiles (), token); - loaded_files = loaded.Value? ["result"]? ["value"]?.ToObject (); - } - - await foreach (var source in context.store.Load(sessionId, loaded_files, token).WithCancellation (token)) { - var scriptSource = JObject.FromObject (source.ToScriptSource (context.Id, context.AuxData)); - Log ("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}"); - - SendEvent (sessionId, "Debugger.scriptParsed", scriptSource, token); - - foreach (var req in context.BreakpointRequests.Values) { - if (req.TryResolve (source)) { - await SetBreakpoint (sessionId, context.store, req, true, token); - } - } - } - } catch (Exception e) { - context.Source.SetException (e); - } - - if (!context.Source.Task.IsCompleted) - context.Source.SetResult (context.store); - return context.store; - } - - async Task RuntimeReady (SessionId sessionId, CancellationToken token) - { - var context = GetContext (sessionId); - if (Interlocked.CompareExchange (ref context.ready, new TaskCompletionSource (), null) != null) - return await context.ready.Task; - - var clear_result = await SendMonoCommand (sessionId, MonoCommands.ClearAllBreakpoints (), token); - if (clear_result.IsErr) { - Log ("verbose", $"Failed to clear breakpoints due to {clear_result}"); - } - - var store = await LoadStore (sessionId, token); - - context.ready.SetResult (store); - SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token); - return store; - } - - async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) { - var bpid = args? ["breakpointId"]?.Value (); - - var context = GetContext (msg_id); - if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest)) - return; - - foreach (var bp in breakpointRequest.Locations) { - var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token); - var ret_code = res.Value? ["result"]? ["value"]?.Value (); - - if (ret_code.HasValue) { - bp.RemoteId = -1; - bp.State = BreakpointState.Disabled; - } - } - breakpointRequest.Locations.Clear (); - } - - async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token) - { - var context = GetContext (sessionId); - if (req.Locations.Any ()) { - Log ("debug", $"locations already loaded for {req.Id}"); - return; - } - - var comparer = new SourceLocation.LocationComparer (); - // if column is specified the frontend wants the exact matches - // and will clear the bp if it isn't close enoug - var locations = store.FindBreakpointLocations (req) - .Distinct (comparer) - .Where (l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column)) - .OrderBy (l => l.Column) - .GroupBy (l => l.Id); - - logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady); - - var breakpoints = new List (); - - foreach (var sourceId in locations) { - var loc = sourceId.First (); - var bp = await SetMonoBreakpoint (sessionId, req.Id, loc, token); - - // If we didn't successfully enable the breakpoint - // don't add it to the list of locations for this id - if (bp.State != BreakpointState.Active) - continue; - - breakpoints.Add (bp); - - var resolvedLocation = new { - breakpointId = req.Id, - location = loc.AsLocation () - }; - - if (sendResolvedEvent) - SendEvent (sessionId, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token); - } - - req.Locations.AddRange (breakpoints); - return; - } - - async Task GetPossibleBreakpoints (MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token) - { - var bps = (await RuntimeReady (msg, token)).FindPossibleBreakpoints (start, end); - - if (bps == null) - return false; - - var response = new { locations = bps.Select (b => b.AsLocation ()) }; - - SendResponse (msg, Result.OkFromObject (response), token); - return true; - } - - void OnCompileDotnetScript (MessageId msg_id, CancellationToken token) - { - SendResponse (msg_id, Result.OkFromObject (new { }), token); - } - - async Task OnGetScriptSource (MessageId msg_id, string script_id, CancellationToken token) - { - if (!SourceId.TryParse (script_id, out var id)) - return false; - - var src_file = (await LoadStore (msg_id, token)).GetFileById (id); - - try { - var uri = new Uri (src_file.Url); - string source = $"// Unable to find document {src_file.SourceUri}"; - - using (var data = await src_file.GetSourceAsync (checkHash: false, token: token)) { - if (data.Length == 0) - return false; - - using (var reader = new StreamReader (data)) - source = await reader.ReadToEndAsync (); - } - SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token); - } catch (Exception e) { - var o = new { - scriptSource = $"// Unable to read document ({e.Message})\n" + - $"Local path: {src_file?.SourceUri}\n" + - $"SourceLink path: {src_file?.SourceLinkUri}\n" - }; - - SendResponse (msg_id, Result.OkFromObject (o), token); - } - return true; - } - - async Task DeleteWebDriver (SessionId sessionId, CancellationToken token) - { - // see https://github.com/mono/mono/issues/19549 for background - if (hideWebDriver && sessions.Add (sessionId)) { - var res = await SendCommand (sessionId, - "Page.addScriptToEvaluateOnNewDocument", - JObject.FromObject (new { source = "delete navigator.constructor.prototype.webdriver"}), - token); - - if (sessionId != SessionId.Null && !res.IsOk) - sessions.Remove (sessionId); - } - } - } -} diff --git a/sdks/wasm/ProxyDriver/Program.cs b/sdks/wasm/ProxyDriver/Program.cs deleted file mode 100644 index faaa4960c586..000000000000 --- a/sdks/wasm/ProxyDriver/Program.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace WebAssembly.Net.Debugging -{ - public class ProxyOptions { - public Uri DevToolsUrl { get; set; } = new Uri ("http://localhost:9222"); - } - - public class TestHarnessOptions : ProxyOptions { - public string ChromePath { get; set; } - public string AppPath { get; set; } - public string PagePath { get; set; } - public string NodeApp { get; set; } - } - - public class Program { - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseSetting ("UseIISIntegration", false.ToString ()) - .UseKestrel () - .UseContentRoot (Directory.GetCurrentDirectory()) - .UseStartup () - .ConfigureAppConfiguration ((hostingContext, config) => - { - config.AddCommandLine(args); - }) - .UseUrls ("http://localhost:9300") - .Build (); - - host.Run (); - } - } - - public class TestHarnessProxy { - static IWebHost host; - static Task hostTask; - static CancellationTokenSource cts = new CancellationTokenSource (); - static object proxyLock = new object (); - - public static readonly Uri Endpoint = new Uri ("http://localhost:9400"); - - public static Task Start (string chromePath, string appPath, string pagePath) - { - lock (proxyLock) { - if (host != null) - return hostTask; - - host = WebHost.CreateDefaultBuilder () - .UseSetting ("UseIISIntegration", false.ToString ()) - .ConfigureAppConfiguration ((hostingContext, config) => { - config.AddEnvironmentVariables (prefix: "WASM_TESTS_"); - }) - .ConfigureServices ((ctx, services) => { - services.Configure (ctx.Configuration); - services.Configure (options => { - options.ChromePath = options.ChromePath ?? chromePath; - options.AppPath = appPath; - options.PagePath = pagePath; - options.DevToolsUrl = new Uri ("http://localhost:0"); - }); - }) - .UseStartup () - .UseUrls (Endpoint.ToString ()) - .Build(); - hostTask = host.StartAsync (cts.Token); - } - - Console.WriteLine ("WebServer Ready!"); - return hostTask; - } - } -} diff --git a/sdks/wasm/ProxyDriver/Startup.cs b/sdks/wasm/ProxyDriver/Startup.cs deleted file mode 100644 index e18df90c39c7..000000000000 --- a/sdks/wasm/ProxyDriver/Startup.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System.Net.Http; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; - -namespace WebAssembly.Net.Debugging { - internal class Startup { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices (IServiceCollection services) => - services.AddRouting () - .Configure (Configuration); - - public Startup (IConfiguration configuration) => - Configuration = configuration; - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure (IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env) - { - var options = optionsAccessor.CurrentValue; - app.UseDeveloperExceptionPage () - .UseWebSockets () - .UseDebugProxy (options); - } - } - - static class DebugExtensions { - public static Dictionary MapValues (Dictionary response, HttpContext context, Uri debuggerHost) - { - var filtered = new Dictionary (); - var request = context.Request; - - foreach (var key in response.Keys) { - switch (key) { - case "devtoolsFrontendUrl": - var front = response [key]; - filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace ($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; - break; - case "webSocketDebuggerUrl": - var page = new Uri (response [key]); - filtered [key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}"; - break; - default: - filtered [key] = response [key]; - break; - } - } - return filtered; - } - - public static IApplicationBuilder UseDebugProxy (this IApplicationBuilder app, ProxyOptions options) => - UseDebugProxy (app, options, MapValues); - - public static IApplicationBuilder UseDebugProxy ( - this IApplicationBuilder app, - ProxyOptions options, - Func, HttpContext, Uri, Dictionary> mapFunc) - { - var devToolsHost = options.DevToolsUrl; - app.UseRouter (router => { - router.MapGet ("/", Copy); - router.MapGet ("/favicon.ico", Copy); - router.MapGet ("json", RewriteArray); - router.MapGet ("json/list", RewriteArray); - router.MapGet ("json/version", RewriteSingle); - router.MapGet ("json/new", RewriteSingle); - router.MapGet ("devtools/page/{pageId}", ConnectProxy); - router.MapGet ("devtools/browser/{pageId}", ConnectProxy); - - string GetEndpoint (HttpContext context) - { - var request = context.Request; - var requestPath = request.Path; - return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}"; - } - - async Task Copy (HttpContext context) { - using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds (5) }) { - var response = await httpClient.GetAsync (GetEndpoint (context)); - context.Response.ContentType = response.Content.Headers.ContentType.ToString (); - if ((response.Content.Headers.ContentLength ?? 0) > 0) - context.Response.ContentLength = response.Content.Headers.ContentLength; - var bytes = await response.Content.ReadAsByteArrayAsync (); - await context.Response.Body.WriteAsync (bytes); - - } - } - - async Task RewriteSingle (HttpContext context) - { - var version = await ProxyGetJsonAsync> (GetEndpoint (context)); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync ( - JsonSerializer.Serialize (mapFunc (version, context, devToolsHost))); - } - - async Task RewriteArray (HttpContext context) - { - var tabs = await ProxyGetJsonAsync []> (GetEndpoint (context)); - var alteredTabs = tabs.Select (t => mapFunc (t, context, devToolsHost)).ToArray (); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync (JsonSerializer.Serialize (alteredTabs)); - } - - async Task ConnectProxy (HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) { - context.Response.StatusCode = 400; - return; - } - - var endpoint = new Uri ($"ws://{devToolsHost.Authority}{context.Request.Path.ToString ()}"); - try { - using var loggerFactory = LoggerFactory.Create( - builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - var proxy = new DebuggerProxy (loggerFactory); - var ideSocket = await context.WebSockets.AcceptWebSocketAsync (); - - await proxy.Run (endpoint, ideSocket); - } catch (Exception e) { - Console.WriteLine ("got exception {0}", e); - } - } - }); - return app; - } - - static async Task ProxyGetJsonAsync (string url) - { - using (var httpClient = new HttpClient ()) { - var response = await httpClient.GetAsync (url); - return await JsonSerializer.DeserializeAsync (await response.Content.ReadAsStreamAsync ()); - } - } - } -} diff --git a/sdks/wasm/ProxyDriver/TestHarnessStartup.cs b/sdks/wasm/ProxyDriver/TestHarnessStartup.cs deleted file mode 100644 index 6ae4ba32b764..000000000000 --- a/sdks/wasm/ProxyDriver/TestHarnessStartup.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; - -namespace WebAssembly.Net.Debugging { - public class TestHarnessStartup { - static Regex parseConnection = new Regex (@"listening on (ws?s://[^\s]*)"); - public TestHarnessStartup (IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; set; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices (IServiceCollection services) - { - services.AddRouting () - .Configure (Configuration); - } - - async Task SendNodeVersion (HttpContext context) - { - Console.WriteLine ("hello chrome! json/version"); - var resp_obj = new JObject (); - resp_obj ["Browser"] = "node.js/v9.11.1"; - resp_obj ["Protocol-Version"] = "1.1"; - - var response = resp_obj.ToString (); - await context.Response.WriteAsync (response, new CancellationTokenSource ().Token); - } - - async Task SendNodeList (HttpContext context) - { - Console.WriteLine ("hello chrome! json/list"); - try { - var response = new JArray (JObject.FromObject (new { - description = "node.js instance", - devtoolsFrontendUrl = "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4", - faviconUrl = "https://nodejs.org/static/favicon.ico", - id = "91d87807-8a81-4f49-878c-a5604103b0a4", - title = "foo.js", - type = "node", - webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4" - })).ToString (); - - Console.WriteLine ($"sending: {response}"); - await context.Response.WriteAsync (response, new CancellationTokenSource ().Token); - } catch (Exception e) { Console.WriteLine (e); } - } - - public async Task LaunchAndServe (ProcessStartInfo psi, HttpContext context, Func> extract_conn_url) - { - - if (!context.WebSockets.IsWebSocketRequest) { - context.Response.StatusCode = 400; - return; - } - - var tcs = new TaskCompletionSource (); - - var proc = Process.Start (psi); - try { - proc.ErrorDataReceived += (sender, e) => { - var str = e.Data; - Console.WriteLine ($"stderr: {str}"); - - if (tcs.Task.IsCompleted) - return; - - var match = parseConnection.Match (str); - if (match.Success) { - tcs.TrySetResult (match.Groups[1].Captures[0].Value); - } - }; - - proc.OutputDataReceived += (sender, e) => { - Console.WriteLine ($"stdout: {e.Data}"); - }; - - proc.BeginErrorReadLine (); - proc.BeginOutputReadLine (); - - if (await Task.WhenAny (tcs.Task, Task.Delay (5000)) != tcs.Task) { - Console.WriteLine ("Didnt get the con string after 5s."); - throw new Exception ("node.js timedout"); - } - var line = await tcs.Task; - var con_str = extract_conn_url != null ? await extract_conn_url (line) : line; - - Console.WriteLine ($"launching proxy for {con_str}"); - - using var loggerFactory = LoggerFactory.Create( - builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - var proxy = new DebuggerProxy (loggerFactory); - var browserUri = new Uri (con_str); - var ideSocket = await context.WebSockets.AcceptWebSocketAsync (); - - await proxy.Run (browserUri, ideSocket); - Console.WriteLine("Proxy done"); - } catch (Exception e) { - Console.WriteLine ("got exception {0}", e); - } finally { - proc.CancelErrorRead (); - proc.CancelOutputRead (); - proc.Kill (); - proc.WaitForExit (); - proc.Close (); - } - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure (IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env) - { - app.UseWebSockets (); - app.UseStaticFiles (); - - TestHarnessOptions options = optionsAccessor.CurrentValue; - - var provider = new FileExtensionContentTypeProvider(); - provider.Mappings [".wasm"] = "application/wasm"; - - app.UseStaticFiles (new StaticFileOptions { - FileProvider = new PhysicalFileProvider (options.AppPath), - ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry: - RequestPath = "", - ContentTypeProvider = provider - }); - - var devToolsUrl = options.DevToolsUrl; - app.UseRouter (router => { - router.MapGet ("launch-chrome-and-connect", async context => { - Console.WriteLine ("New test request"); - try { - var client = new HttpClient (); - var psi = new ProcessStartInfo (); - - psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}"; - psi.UseShellExecute = false; - psi.FileName = options.ChromePath; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; - - - await LaunchAndServe (psi, context, async (str) => { - var start = DateTime.Now; - JArray obj = null; - - while (true) { - // Unfortunately it does look like we have to wait - // for a bit after getting the response but before - // making the list request. We get an empty result - // if we make the request too soon. - await Task.Delay (100); - - var res = await client.GetStringAsync (new Uri (new Uri (str), "/json/list")); - Console.WriteLine ("res is {0}", res); - - if (!String.IsNullOrEmpty (res)) { - // Sometimes we seem to get an empty array `[ ]` - obj = JArray.Parse (res); - if (obj != null && obj.Count >= 1) - break; - } - - var elapsed = DateTime.Now - start; - if (elapsed.Milliseconds > 5000) { - Console.WriteLine ($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping"); - return null; - } - } - - var wsURl = obj[0]? ["webSocketDebuggerUrl"]?.Value (); - Console.WriteLine (">>> {0}", wsURl); - - return wsURl; - }); - } catch (Exception ex) { - Console.WriteLine ($"launch-chrome-and-connect failed with {ex.ToString ()}"); - } - }); - }); - - if (options.NodeApp != null) { - Console.WriteLine($"Doing the nodejs: {options.NodeApp}"); - var nodeFullPath = Path.GetFullPath (options.NodeApp); - Console.WriteLine (nodeFullPath); - var psi = new ProcessStartInfo (); - - psi.UseShellExecute = false; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; - - psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}"; - psi.FileName = "node"; - - app.UseRouter (router => { - //Inspector API for using chrome devtools directly - router.MapGet ("json", SendNodeList); - router.MapGet ("json/list", SendNodeList); - router.MapGet ("json/version", SendNodeVersion); - router.MapGet ("launch-done-and-connect", async context => { - await LaunchAndServe (psi, context, null); - }); - }); - } - } - } -} diff --git a/sdks/wasm/README.md b/sdks/wasm/README.md index ff1bb31805b0..95a3d1331242 100644 --- a/sdks/wasm/README.md +++ b/sdks/wasm/README.md @@ -197,7 +197,7 @@ To experiment with the debugger, do the following steps: - When calling `packager.exe` pass the `-debug` argument to it. - Start Chrome with remote debugging enabled (IE `/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222`) -- Run the proxy: `dotnet run -p ProxyDriver/ProxyDriver.csproj` +- Run the proxy: `dotnet run -p BrowserDebugHost/BrowserDebugHost.csproj` - Connect to the proxy by visiting http://localhost:9300/ and select the tab you wish to debug from the list of tabs - Refresh the debugged page and you should be set diff --git a/sdks/wasm/other.js b/sdks/wasm/other.js index 93e091f7ad5c..d3a6d3989726 100644 --- a/sdks/wasm/other.js +++ b/sdks/wasm/other.js @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + function big_array_js_test (len) { var big = new Array(len); for (let i=0; i < len; i ++) { @@ -27,4 +30,49 @@ function getters_js_test () { }; console.log (`break here`); return ptd; -} \ No newline at end of file +} + +function exception_caught_test () { + try { + throw new TypeError ('exception caught'); + } catch (e) { + console.log(e); + } +} + +function exception_uncaught_test () { + console.log('uncaught test'); + throw new RangeError ('exception uncaught'); +} + +function exceptions_test () { + exception_caught_test (); + exception_uncaught_test (); +} + +function negative_cfo_test (str_value = null) { + var ptd = { + get Int () { return 5; }, + get String () { return "foobar"; }, + get DT () { return "dt"; }, + get IntArray () { return [1,2,3]; }, + get DTArray () { return ["dt0", "dt1"]; }, + DTAutoProperty: "dt", + StringField: str_value + }; + console.log (`break here`); + return ptd; +} + +function eval_call_on_frame_test () { + let obj = { + a: 5, + b: "hello", + c: { + c_x: 1 + }, + }; + + let obj_undefined = undefined; + console.log(`break here`); +} diff --git a/sdks/wasm/package-update/download-packages.ps1 b/sdks/wasm/package-update/download-packages.ps1 index 2c12a12d0826..1452bcd2f1b2 100644 --- a/sdks/wasm/package-update/download-packages.ps1 +++ b/sdks/wasm/package-update/download-packages.ps1 @@ -40,7 +40,7 @@ $NUGET_HOME="~\\.nuget" $PACKAGE_PATH="$NUGET_HOME\\packages\\microsoft.aspnetcore.components.webassembly.runtime\\$runtime\\tools\\dotnetwasm" $PROXY_PACKAGE_PATH="$NUGET_HOME\\packages\\microsoft.aspnetcore.components.webassembly.devserver\\$runtime\\tools\\BlazorDebugProxy" $ASP_PROXY_PATH="$asp_working_dir\\src\\Components\\WebAssembly\\DebugProxy\\src" -$MONO_PROXY_PATH="$mono_working_dir\\sdks\\wasm\\Mono.WebAssembly.DebuggerProxy" +$MONO_PROXY_PATH="$mono_working_dir\\sdks\\wasm\\BrowserDebugProxy" $TMP_DIR=(mktemp -d) $TMP_PKG_DIR="$TMP_DIR\\wasm-package" mkdir $TMP_DIR diff --git a/sdks/wasm/package-update/download-packages.sh b/sdks/wasm/package-update/download-packages.sh index 22350c6913af..ad83c462f304 100644 --- a/sdks/wasm/package-update/download-packages.sh +++ b/sdks/wasm/package-update/download-packages.sh @@ -63,7 +63,7 @@ NUGET_HOME=${NUGET_HOME:-"$HOME/.nuget"} PACKAGE_PATH="$NUGET_HOME/packages/microsoft.aspnetcore.components.webassembly.runtime/$RUNTIME_VER/tools/dotnetwasm" PROXY_PACKAGE_PATH="$NUGET_HOME/packages/microsoft.aspnetcore.components.webassembly.devserver/$RUNTIME_VER/tools/BlazorDebugProxy" ASP_PROXY_PATH="$ASPNETCORE/src/Components/WebAssembly/DebugProxy/src" -MONO_PROXY_PATH="$MONO/sdks/wasm/Mono.WebAssembly.DebuggerProxy" +MONO_PROXY_PATH="$MONO/sdks/wasm/BrowserDebugProxy" TMP_DIR=`mktemp -d` TMP_PKG_DIR=$TMP_DIR/wasm-package mkdir $TMP_PKG_DIR diff --git a/sdks/wasm/packager.cs b/sdks/wasm/packager.cs index ede46f44f6a8..28d4f53dc652 100644 --- a/sdks/wasm/packager.cs +++ b/sdks/wasm/packager.cs @@ -145,6 +145,7 @@ static void Usage () { Console.WriteLine ("\t--native-lib=x Link the native library 'x' into the final executable."); Console.WriteLine ("\t--preload-file=x Preloads the file or directory 'x' into the virtual filesystem."); Console.WriteLine ("\t--embed-file=x Embeds the file or directory 'x' into the virtual filesystem."); + Console.WriteLine ("\t--extra-emcc-flags=\"x\" Additional flags to pass to emcc."); Console.WriteLine ("foo.dll Include foo.dll as one of the root assemblies"); Console.WriteLine (); @@ -407,11 +408,13 @@ int Run (string[] args) { var il_strip = false; var linker_verbose = false; var runtimeTemplate = "runtime.js"; + var runtimeTemplateOutputName = "runtime.js"; var assets = new List (); var profilers = new List (); var native_libs = new List (); var preload_files = new List (); var embed_files = new List (); + var extra_emcc_flags = ""; var pinvoke_libs = ""; var copyTypeParm = "default"; var copyType = CopyType.Default; @@ -456,6 +459,7 @@ int Run (string[] args) { { "aot", s => ee_mode = ExecMode.Aot }, { "aot-interp", s => ee_mode = ExecMode.AotInterp }, { "template=", s => runtimeTemplate = s }, + { "template-output-name=", s => runtimeTemplateOutputName = s }, { "asset=", s => assets.Add(s) }, { "search-path=", s => root_search_paths.Add(s) }, { "profile=", s => profilers.Add (s) }, @@ -468,6 +472,7 @@ int Run (string[] args) { { "native-lib=", s => native_libs.Add (s) }, { "preload-file=", s => preload_files.Add (s) }, { "embed-file=", s => embed_files.Add (s) }, + { "extra-emcc-flags=", s => extra_emcc_flags = s }, { "framework=", s => framework = s }, { "help", s => print_usage = true }, }; @@ -686,7 +691,7 @@ int Run (string[] args) { wasm_core_support = BINDINGS_MODULE_SUPPORT; wasm_core_support_library = $"--js-library {BINDINGS_MODULE_SUPPORT}"; } - var runtime_js = Path.Combine (emit_ninja ? builddir : out_prefix, "runtime.js"); + var runtime_js = Path.Combine (emit_ninja ? builddir : out_prefix, runtimeTemplateOutputName); if (emit_ninja) { File.Delete (runtime_js); File.Copy (runtimeTemplate, runtime_js); @@ -856,6 +861,8 @@ int Run (string[] args) { emcc_flags += "--preload-file " + pf + " "; foreach (var f in embed_files) emcc_flags += "--embed-file " + f + " "; + if (!String.IsNullOrEmpty (extra_emcc_flags)) + emcc_flags += extra_emcc_flags + " "; string emcc_link_flags = ""; if (enable_debug) emcc_link_flags += "-O0 "; @@ -948,7 +955,7 @@ int Run (string[] args) { // Targets ninja.WriteLine ("build $appdir: mkdir"); ninja.WriteLine ("build $appdir/$deploy_prefix: mkdir"); - ninja.WriteLine ("build $appdir/runtime.js: cpifdiff $builddir/runtime.js"); + ninja.WriteLine ($"build $appdir/{runtimeTemplateOutputName}: cpifdiff $builddir/{runtimeTemplateOutputName}"); ninja.WriteLine ("build $appdir/mono-config.js: cpifdiff $builddir/mono-config.js"); if (build_wasm) { string src_prefix; diff --git a/sdks/wasm/src/library_mono.js b/sdks/wasm/src/library_mono.js index 6569fd985d05..eccc137f7b1a 100644 --- a/sdks/wasm/src/library_mono.js +++ b/sdks/wasm/src/library_mono.js @@ -1,5 +1,45 @@ -/* jshint esversion: 6 */ -/* jshint evil: true */ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/** + * @typedef WasmId + * @type {object} + * @property {string} idStr - full object id string + * @property {string} scheme - eg, object, valuetype, array .. + * @property {string} value - string part after `dotnet:scheme:` of the id string + * @property {object} o - value parsed as JSON + */ + +/** + * @typedef WasmRoot - a single address in the managed heap, visible to the GC + * @type {object} + * @property {ManagedPointer} value - pointer into the managed heap, stored in the root + * @property {function} get_address - retrieves address of the root in wasm memory + * @property {function} get - retrieves pointer value + * @property {function} set - updates the pointer + * @property {function} release - releases the root storage for future use + */ + +/** + * @typedef WasmRootBuffer - a collection of addresses in the managed heap, visible to the GC + * @type {object} + * @property {number} length - number of elements the root buffer can hold + * @property {function} get_address - retrieves address of an element in wasm memory, by index + * @property {function} get - retrieves an element by index + * @property {function} set - sets an element's value by index + * @property {function} release - releases the root storage for future use + */ + +/** + * @typedef ManagedPointer + * @type {number} - address in the managed heap + */ + +/** + * @typedef NativePointer + * @type {number} - address in wasm memory + */ + var MonoSupportLib = { $MONO__postset: 'MONO.export_functions (Module);', $MONO: { @@ -8,6 +48,10 @@ var MonoSupportLib = { _vt_stack: [], mono_wasm_runtime_is_ready : false, mono_wasm_ignore_pdb_load_errors: true, + + /** @type {object.} */ + _id_table: {}, + pump_message: function () { if (!this.mono_background_exec) this.mono_background_exec = Module.cwrap ("mono_background_exec", null); @@ -24,6 +68,227 @@ var MonoSupportLib = { export_functions: function (module) { module ["pump_message"] = MONO.pump_message; module ["mono_load_runtime_and_bcl"] = MONO.mono_load_runtime_and_bcl; + module ["mono_load_runtime_and_bcl_args"] = MONO.mono_load_runtime_and_bcl_args; + module ["mono_wasm_load_bytes_into_heap"] = MONO.mono_wasm_load_bytes_into_heap; + module ["mono_wasm_load_icu_data"] = MONO.mono_wasm_load_icu_data; + module ["mono_wasm_globalization_init"] = MONO.mono_wasm_globalization_init; + module ["mono_wasm_get_loaded_files"] = MONO.mono_wasm_get_loaded_files; + module ["mono_wasm_new_root_buffer"] = MONO.mono_wasm_new_root_buffer; + module ["mono_wasm_new_root"] = MONO.mono_wasm_new_root; + module ["mono_wasm_new_roots"] = MONO.mono_wasm_new_roots; + module ["mono_wasm_release_roots"] = MONO.mono_wasm_release_roots; + }, + + _mono_wasm_root_buffer_prototype: { + _check_in_range: function (index) { + if ((index >= this.__count) || (index < 0)) + throw new Error ("index out of range"); + }, + /** @returns {NativePointer} */ + get_address: function (index) { + this._check_in_range (index); + return this.__offset + (index * 4); + }, + /** @returns {number} */ + get_address_32: function (index) { + this._check_in_range (index); + return this.__offset32 + index; + }, + /** @returns {ManagedPointer} */ + get: function (index) { + this._check_in_range (index); + return Module.HEAP32[this.get_address_32 (index)]; + }, + set: function (index, value) { + this._check_in_range (index); + Module.HEAP32[this.get_address_32 (index)] = value; + return value; + }, + release: function () { + if (this.__offset) { + MONO.mono_wasm_deregister_root (this.__offset); + MONO._zero_region (this.__offset, this.__count * 4); + Module._free (this.__offset); + } + + this.__handle = this.__offset = this.__count = this.__offset32 = undefined; + }, + }, + + _scratch_root_buffer: null, + _scratch_root_free_indices: null, + + _mono_wasm_root_prototype: { + /** @returns {NativePointer} */ + get_address: function () { + return this.__buffer.get_address (this.__index); + }, + /** @returns {number} */ + get_address_32: function () { + return this.__buffer.get_address_32 (this.__index); + }, + /** @returns {ManagedPointer} */ + get: function () { + var result = this.__buffer.get (this.__index); + return result; + }, + set: function (value) { + this.__buffer.set (this.__index, value); + return value; + }, + /** @returns {ManagedPointer} */ + valueOf: function () { + return this.get (); + }, + release: function () { + MONO._mono_wasm_release_scratch_index (this.__index); + this.__buffer = undefined; + this.__index = undefined; + } + }, + + _mono_wasm_release_scratch_index: function (index) { + if (index === undefined) + return; + + this._scratch_root_buffer.set (index, 0); + this._scratch_root_free_indices.push (index); + }, + + _mono_wasm_claim_scratch_index: function () { + if (!this._scratch_root_buffer) { + const maxScratchRoots = 8192; + this._scratch_root_buffer = this.mono_wasm_new_root_buffer (maxScratchRoots, "js roots"); + + this._scratch_root_free_indices = new Array (maxScratchRoots); + for (var i = 0; i < maxScratchRoots; i++) + this._scratch_root_free_indices[i] = i; + this._scratch_root_free_indices.reverse (); + + Object.defineProperty (this._mono_wasm_root_prototype, "value", { + get: this._mono_wasm_root_prototype.get, + set: this._mono_wasm_root_prototype.set, + configurable: false + }); + } + + if (this._scratch_root_free_indices.length < 1) + throw new Error ("Out of scratch root space"); + + var result = this._scratch_root_free_indices.pop (); + return result; + }, + + _zero_region: function (byteOffset, sizeBytes) { + (new Uint8Array (Module.HEAPU8.buffer, byteOffset, sizeBytes)).fill (0); + }, + + /** + * Allocates a block of memory that can safely contain pointers into the managed heap. + * The result object has get(index) and set(index, value) methods that can be used to retrieve and store managed pointers. + * Once you are done using the root buffer, you must call its release() method. + * For small numbers of roots, it is preferable to use the mono_wasm_new_root and mono_wasm_new_roots APIs instead. + * @param {number} capacity - the maximum number of elements the buffer can hold. + * @param {string} [msg] - a description of the root buffer (for debugging) + * @returns {WasmRootBuffer} + */ + mono_wasm_new_root_buffer: function (capacity, msg) { + if (!this.mono_wasm_register_root || !this.mono_wasm_deregister_root) { + this.mono_wasm_register_root = Module.cwrap ("mono_wasm_register_root", "number", ["number", "number", "string"]); + this.mono_wasm_deregister_root = Module.cwrap ("mono_wasm_deregister_root", null, ["number"]); + } + + if (capacity <= 0) + throw new Error ("capacity >= 1"); + + capacity = capacity | 0; + + var capacityBytes = capacity * 4; + var offset = Module._malloc (capacityBytes); + if ((offset % 4) !== 0) + throw new Error ("Malloc returned an unaligned offset"); + + this._zero_region (offset, capacityBytes); + + var result = Object.create (this._mono_wasm_root_buffer_prototype); + result.__offset = offset; + result.__offset32 = (offset / 4) | 0; + result.__count = capacity; + result.length = capacity; + result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0); + + return result; + }, + + /** + * Allocates temporary storage for a pointer into the managed heap. + * Pointers stored here will be visible to the GC, ensuring that the object they point to aren't moved or collected. + * If you already have a managed pointer you can pass it as an argument to initialize the temporary storage. + * The result object has get() and set(value) methods, along with a .value property. + * When you are done using the root you must call its .release() method. + * @param {ManagedPointer} [value] - an address in the managed heap to initialize the root with (or 0) + * @returns {WasmRoot} + */ + mono_wasm_new_root: function (value) { + var index = this._mono_wasm_claim_scratch_index (); + var buffer = this._scratch_root_buffer; + + var result = Object.create (this._mono_wasm_root_prototype); + result.__buffer = buffer; + result.__index = index; + + if (value !== undefined) { + if (typeof (value) !== "number") + throw new Error ("value must be an address in the managed heap"); + + result.set (value); + } else { + result.set (0); + } + + return result; + }, + + /** + * Allocates 1 or more temporary roots, accepting either a number of roots or an array of pointers. + * mono_wasm_new_roots(n): returns an array of N zero-initialized roots. + * mono_wasm_new_roots([a, b, ...]) returns an array of new roots initialized with each element. + * Each root must be released with its release method, or using the mono_wasm_release_roots API. + * @param {(number | ManagedPointer[])} count_or_values - either a number of roots or an array of pointers + * @returns {WasmRoot[]} + */ + mono_wasm_new_roots: function (count_or_values) { + var result; + + if (Array.isArray (count_or_values)) { + result = new Array (count_or_values.length); + for (var i = 0; i < result.length; i++) + result[i] = this.mono_wasm_new_root (count_or_values[i]); + } else if ((count_or_values | 0) > 0) { + result = new Array (count_or_values); + for (var i = 0; i < result.length; i++) + result[i] = this.mono_wasm_new_root (); + } else { + throw new Error ("count_or_values must be either an array or a number greater than 0"); + } + + return result; + }, + + /** + * Releases 1 or more root or root buffer objects. + * Multiple objects may be passed on the argument list. + * 'undefined' may be passed as an argument so it is safe to call this method from finally blocks + * even if you are not sure all of your roots have been created yet. + * @param {... WasmRoot} roots + */ + mono_wasm_release_roots: function () { + for (var i = 0; i < arguments.length; i++) { + if (!arguments[i]) + continue; + + arguments[i].release (); + } }, mono_text_decoder: undefined, @@ -43,11 +308,11 @@ var MonoSupportLib = { decode: function (start, end, save) { if (!MONO.mono_text_decoder) { MONO.mono_text_decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : undefined; - } + } var str = ""; if (MONO.mono_text_decoder) { - // When threading is enabled, TextDecoder does not accept a view of a + // When threading is enabled, TextDecoder does not accept a view of a // SharedArrayBuffer, we must make a copy of the array first. var subArray = typeof SharedArrayBuffer !== 'undefined' && Module.HEAPU8.buffer instanceof SharedArrayBuffer ? Module.HEAPU8.slice(start, end) @@ -67,6 +332,12 @@ var MonoSupportLib = { }, }, + mono_wasm_get_exception_object: function() { + var exception_obj = MONO.active_exception; + MONO.active_exception = null; + return exception_obj ; + }, + mono_wasm_get_call_stack: function() { if (!this.mono_wasm_current_bp_id) this.mono_wasm_current_bp_id = Module.cwrap ("mono_wasm_current_bp_id", 'number'); @@ -86,30 +357,19 @@ var MonoSupportLib = { }, _fixup_name_value_objects: function (var_list) { - var out_list = []; - - var _fixup_value = function (value) { - if (value != null && value != undefined) { - var descr = value.description; - if (descr == null || descr == undefined) - value.description = '' + value.value; - } - return value; - }; + let out_list = []; var i = 0; while (i < var_list.length) { - var o = var_list [i]; - var name = o.name; + let o = var_list [i]; + const name = o.name; if (name == null || name == undefined) { i ++; - o.value = _fixup_value(o.value); out_list.push (o); continue; } if (i + 1 < var_list.length) { - _fixup_value(var_list[i + 1].value); o = Object.assign (o, var_list [i + 1]); } @@ -121,8 +381,8 @@ var MonoSupportLib = { }, _filter_automatic_properties: function (props) { - var names_found = {}; - var final_var_list = []; + let names_found = {}; + let final_var_list = []; for (var i in props) { var p = props [i]; @@ -141,48 +401,183 @@ var MonoSupportLib = { return final_var_list; }, - // code from https://stackoverflow.com/a/5582308 - // - // Given `dotnet:object:foo:bar`, - // returns [ 'dotnet', 'object', 'foo:bar'] - _split_object_id: function (id, delim = ':', count = 3) { - if (id === undefined || id == "") - return []; + /** Given `dotnet:object:foo:bar`, + * returns { scheme:'object', value: 'foo:bar' } + * + * Given `dotnet:pointer:{ b: 3 }` + * returns { scheme:'object', value: '{b:3}`, o: {b:3} + * + * @param {string} idStr + * @param {boolean} [throwOnError=false] + * + * @returns {WasmId} + */ + _parse_object_id: function (idStr, throwOnError = false) { + if (idStr === undefined || idStr == "" || !idStr.startsWith ('dotnet:')) { + if (throwOnError) + throw new Error (`Invalid id: ${idStr}`); + + return undefined; + } + + const [, scheme, ...rest] = idStr.split(':'); + let res = { + scheme, + value: rest.join (':'), + idStr, + o: {} + }; - if (delim === undefined) delim = ':'; - if (count === undefined) count = 3; + try { + res.o = JSON.parse(res.value); + // eslint-disable-next-line no-empty + } catch (e) {} - var arr = id.split (delim); - var result = arr.splice (0, count - 1); + return res; + }, - if (arr.length > 0) - result.push (arr.join (delim)); - return result; + _resolve_member_by_name: function (base_object, base_name, expr_parts) { + if (base_object === undefined || base_object.value === undefined) + throw new Error(`Bug: base_object is undefined`); + + if (base_object.value.type === 'object' && base_object.value.subtype === 'null') + throw new ReferenceError(`Null reference: ${base_name} is null`); + + if (base_object.value.type !== 'object') + throw new ReferenceError(`'.' is only supported on non-primitive types. Failed on '${base_name}'`); + + if (expr_parts.length == 0) + throw new Error(`Invalid member access expression`);//FIXME: need the full expression here + + const root = expr_parts[0]; + const props = this.mono_wasm_get_details(base_object.value.objectId, {}); + let resObject = props.find(l => l.name == root); + if (resObject !== undefined) { + if (resObject.value === undefined && resObject.get !== undefined) + resObject = this._invoke_getter(base_object.value.objectId, root); + } + + if (resObject === undefined || expr_parts.length == 1) + return resObject; + else { + expr_parts.shift(); + return this._resolve_member_by_name(resObject, root, expr_parts); + } + }, + + mono_wasm_eval_member_access: function (scope, var_list, rootObjectId, expr) { + if (expr === undefined || expr.length == 0) + throw new Error(`expression argument required`); + + let parts = expr.split('.'); + if (parts.length == 0) + throw new Error(`Invalid member access expression: ${expr}`); + + const root = parts[0]; + + const locals = this.mono_wasm_get_variables(scope, var_list); + let rootObject = locals.find(l => l.name === root); + if (rootObject === undefined) { + // check `this` + const thisObject = locals.find(l => l.name == "this"); + if (thisObject === undefined) + throw new ReferenceError(`Could not find ${root} in locals, and no 'this' found.`); + + const thisProps = this.mono_wasm_get_details(thisObject.value.objectId, {}); + rootObject = thisProps.find(tp => tp.name == root); + if (rootObject === undefined) + throw new ReferenceError(`Could not find ${root} in locals, or in 'this'`); + + if (rootObject.value === undefined && rootObject.get !== undefined) + rootObject = this._invoke_getter(thisObject.value.objectId, root); + } + + parts.shift(); + + if (parts.length == 0) + return rootObject; + + if (rootObject === undefined || rootObject.value === undefined) + throw new Error(`Could not get a value for ${root}`); + + return this._resolve_member_by_name(rootObject, root, parts); + }, + + /** + * @param {WasmId} id + * @returns {object[]} + */ + _get_vt_properties: function (id) { + let entry = this._id_table [id.idStr]; + if (entry !== undefined && entry.members !== undefined) + return entry.members; + + if (!isNaN (id.o.containerId)) + this._get_object_properties (id.o.containerId, true); + else if (!isNaN (id.o.arrayId)) + this._get_array_values (id, Number (id.o.arrayIdx), 1, true); + else + throw new Error (`Invalid valuetype id (${id.idStr}). Can't get properties for it.`); + + entry = this._get_id_props (id.idStr); + if (entry !== undefined && entry.members !== undefined) + return entry.members; + + throw new Error (`Unknown valuetype id: ${id.idStr}`); + }, + + /** + * + * @callback GetIdArgsCallback + * @param {object} var + * @param {number} idx + * @returns {object} + */ + + /** + * @param {object[]} vars + * @param {GetIdArgsCallback} getIdArgs + * @returns {object} + */ + _assign_vt_ids: function (vars, getIdArgs) + { + vars.forEach ((v, i) => { + // we might not have a `.value`, like in case of getters which have a `.get` instead + const value = v.value; + if (value === undefined || !value.isValueType) + return; + + if (value.objectId !== undefined) + throw new Error (`Bug: Trying to assign valuetype id, but the var already has one: ${v}`); + + value.objectId = this._new_or_add_id_props ({ scheme: 'valuetype', idArgs: getIdArgs (v, i), props: value._props }); + delete value._props; + }); + + return vars; }, // // @var_list: [ { index: , name: }, .. ] mono_wasm_get_variables: function(scope, var_list) { - if (!this.mono_wasm_get_var_info) - this.mono_wasm_get_var_info = Module.cwrap ("mono_wasm_get_var_info", null, [ 'number', 'number', 'number']); - - this.var_info = []; - var numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT; - var ptr = Module._malloc(numBytes); - var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes); + const numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT; + const ptr = Module._malloc(numBytes); + let heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes); for (let i=0; i ({ containerId: this._async_method_objectId, fieldOffset: v.fieldOffset })); - var value = res[i].value; + for (let i in res) { + const res_name = res [i].name; if (this._async_method_objectId != 0) { //Async methods are special in the way that local variables can be lifted to generated class fields //value of "this" comes here either @@ -191,9 +586,6 @@ var MonoSupportLib = { // ALTHOUGH, the name wouldn't have `<>` for method args res [i].name = res_name.substring (1, res_name.indexOf ('>')); } - - if (value.isValueType) - value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`; } else if (res_name === undefined && var_list [i] !== undefined) { // For non-async methods, we just have the var id, but we have the name // from the caller @@ -202,69 +594,49 @@ var MonoSupportLib = { } this._post_process_details(res); - this.var_info = [] - return res; }, - mono_wasm_get_object_properties: function(objId, expandValueTypes) { - if (!this.mono_wasm_get_object_properties_info) - this.mono_wasm_get_object_properties_info = Module.cwrap ("mono_wasm_get_object_properties", null, [ 'number', 'bool' ]); - - this.var_info = []; - this.mono_wasm_get_object_properties_info (objId, expandValueTypes); - - var res = MONO._filter_automatic_properties (MONO._fixup_name_value_objects (this.var_info)); - for (var i = 0; i < res.length; i++) { - var res_val = res [i].value; - // we might not have a `.value`, like in case of getters which have a `.get` instead - if (res_val !== undefined && res_val.isValueType != undefined && res_val.isValueType) - res_val.objectId = `dotnet:valuetype:${objId}:${res [i].fieldOffset}`; - } - - this.var_info = []; + /** + * @param {number} idNum + * @param {boolean} expandValueTypes + * @returns {object} + */ + _get_object_properties: function(idNum, expandValueTypes) { + let { res_ok, res } = this.mono_wasm_get_object_properties_info (idNum, expandValueTypes); + if (!res_ok) + throw new Error (`Failed to get properties for ${idNum}`); + + res = MONO._filter_automatic_properties (res); + res = this._assign_vt_ids (res, v => ({ containerId: idNum, fieldOffset: v.fieldOffset })); + res = this._post_process_details (res); return res; }, - mono_wasm_get_array_values: function(objId) { - if (!this.mono_wasm_get_array_values_info) - this.mono_wasm_get_array_values_info = Module.cwrap ("mono_wasm_get_array_values", null, [ 'number' ]); - - this.var_info = []; - this.mono_wasm_get_array_values_info (objId); - - var res = MONO._fixup_name_value_objects (this.var_info); - for (var i = 0; i < res.length; i++) { - var prop_value = res [i].value; - if (prop_value.isValueType) { - res [i].value.objectId = `dotnet:array:${objId}:${i}`; - } else if (prop_value.objectId !== undefined && prop_value.objectId.startsWith("dotnet:pointer")) { - prop_value.objectId = this._get_updated_ptr_id (prop_value.objectId, { - varName: `[${i}]` - }); - } + /** + * @param {WasmId} id + * @param {number} [startIdx=0] + * @param {number} [count=-1] + * @param {boolean} [expandValueTypes=false] + * @returns {object[]} + */ + _get_array_values: function (id, startIdx = 0, count = -1, expandValueTypes = false) { + if (isNaN (id.o.arrayId) || isNaN (startIdx)) + throw new Error (`Invalid array id: ${id.idStr}`); + + let { res_ok, res } = this.mono_wasm_get_array_values_info (id.o.arrayId, startIdx, count, expandValueTypes); + if (!res_ok) + throw new Error (`Failed to get properties for array id ${id.idStr}`); + + res = this._assign_vt_ids (res, (_, i) => ({ arrayId: id.o.arrayId, arrayIdx: Number (startIdx) + i})); + + for (let i = 0; i < res.length; i ++) { + let value = res [i].value; + if (value.objectId !== undefined && value.objectId.startsWith("dotnet:pointer")) + this._new_or_add_id_props ({ objectId: value.objectId, props: { varName: `[${i}]` } }); } - - this.var_info = []; - - return res; - }, - - mono_wasm_get_array_value_expanded: function(objId, idx) { - if (!this.mono_wasm_get_array_value_expanded_info) - this.mono_wasm_get_array_value_expanded_info = Module.cwrap ("mono_wasm_get_array_value_expanded", null, [ 'number', 'number' ]); - - this.var_info = []; - this.mono_wasm_get_array_value_expanded_info (objId, idx); - - var res = MONO._fixup_name_value_objects (this.var_info); - // length should be exactly one! - if (res [0].value.isValueType != undefined && res [0].value.isValueType) - res [0].value.objectId = `dotnet:array:${objId}:${idx}`; - - this.var_info = []; - + res = this._post_process_details (res); return res; }, @@ -278,8 +650,13 @@ var MonoSupportLib = { return details; }, - _next_value_type_id: function () { - return ++this._next_value_type_id_var; + /** + * Gets the next id number to use for generating ids + * + * @returns {number} + */ + _next_id: function () { + return ++this._next_id_var; }, _extract_and_cache_value_types: function (var_list) { @@ -287,82 +664,46 @@ var MonoSupportLib = { return var_list; for (let i in var_list) { - var value = var_list [i].value; + let value = var_list [i].value; if (value === undefined) continue; if (value.objectId !== undefined && value.objectId.startsWith ("dotnet:pointer:")) { - var ptr_args = this._get_ptr_args (value.objectId); - if (ptr_args.varName === undefined) { - // It might have been already set in some cases, like arrays - // where the name would be `0`, but we want `[0]` for pointers, - // so the deref would look like `*[0]` - value.objectId = this._get_updated_ptr_id (value.objectId, { - varName: var_list [i].name - }); - } + let ptr_args = this._get_id_props (value.objectId); + if (ptr_args === undefined) + throw new Error (`Bug: Expected to find an entry for pointer id: ${value.objectId}`); + + // It might have been already set in some cases, like arrays + // where the name would be `0`, but we want `[0]` for pointers, + // so the deref would look like `*[0]` + ptr_args.varName = ptr_args.varName || var_list [i].name; } if (value.type != "object" || value.isValueType != true || value.expanded != true) // undefined would also give us false continue; // Generate objectId for expanded valuetypes - - var objectId = value.objectId; - if (objectId == undefined) - objectId = `dotnet:valuetype:${this._next_value_type_id ()}`; - value.objectId = objectId; + value.objectId = value.objectId || this._new_or_add_id_props ({ scheme: 'valuetype' }); this._extract_and_cache_value_types (value.members); - this._value_types_cache [objectId] = value.members; + const new_props = Object.assign ({ members: value.members }, value.__extra_vt_props); + + this._new_or_add_id_props ({ objectId: value.objectId, props: new_props }); delete value.members; + delete value.__extra_vt_props; } return var_list; }, - _get_details_for_value_type: function (objectId, fetchDetailsFn) { - if (objectId in this._value_types_cache) - return this._value_types_cache[objectId]; - - this._post_process_details (fetchDetailsFn()); - if (objectId in this._value_types_cache) - return this._value_types_cache[objectId]; - - // return error - throw new Error (`Could not get details for ${objectId}`); - }, - - _is_object_id_array: function (objectId) { - // Keep this in sync with `_get_array_details` - return (objectId.startsWith ('dotnet:array:') && objectId.split (':').length == 3); - }, - - _get_array_details: function (objectId, objectIdParts) { - // Keep this in sync with `_is_object_id_array` - switch (objectIdParts.length) { - case 3: - return this._post_process_details (this.mono_wasm_get_array_values(objectIdParts[2])); - - case 4: - var arrayObjectId = objectIdParts[2]; - var arrayIdx = objectIdParts[3]; - return this._get_details_for_value_type( - objectId, () => this.mono_wasm_get_array_value_expanded(arrayObjectId, arrayIdx)); - - default: - throw new Error (`object id format not supported : ${objectId}`); - } - }, - _get_cfo_res_details: function (objectId, args) { if (!(objectId in this._call_function_res_cache)) throw new Error(`Could not find any object with id ${objectId}`); - var real_obj = this._call_function_res_cache [objectId]; + const real_obj = this._call_function_res_cache [objectId]; - var descriptors = Object.getOwnPropertyDescriptors (real_obj); + const descriptors = Object.getOwnPropertyDescriptors (real_obj); if (args.accessorPropertiesOnly) { Object.keys (descriptors).forEach (k => { if (descriptors [k].get === undefined) @@ -370,10 +711,10 @@ var MonoSupportLib = { }); } - var res_details = []; + let res_details = []; Object.keys (descriptors).forEach (k => { - var new_obj; - var prop_desc = descriptors [k]; + let new_obj; + let prop_desc = descriptors [k]; if (typeof prop_desc.value == "object") { // convert `{value: { type='object', ... }}` // to `{ name: 'foo', value: { type='object', ... }} @@ -414,34 +755,77 @@ var MonoSupportLib = { return { __value_as_json_string__: JSON.stringify (res_details) }; }, - _get_ptr_args: function (objectId) { - var parts = this._split_object_id (objectId); - if (parts.length != 3) - throw new Error (`Bug: Unexpected objectId format for a pointer, expected 3 parts: ${objectId}`); - return JSON.parse (parts [2]); - }, + /** + * Generates a new id, and a corresponding entry for associated properties + * like `dotnet:pointer:{ a: 4 }` + * The third segment of that `{a:4}` is the idArgs parameter + * + * Only `scheme` or `objectId` can be set. + * if `scheme`, then a new id is generated, and it's properties set + * if `objectId`, then it's properties are updated + * + * @param {object} args + * @param {string} [args.scheme=undefined] scheme second part of `dotnet:pointer:..` + * @param {string} [args.objectId=undefined] objectId + * @param {object} [args.idArgs={}] The third segment of the objectId + * @param {object} [args.props={}] Properties for the generated id + * + * @returns {string} generated/updated id string + */ + _new_or_add_id_props: function ({ scheme = undefined, objectId = undefined, idArgs = {}, props = {} }) { + if (scheme === undefined && objectId === undefined) + throw new Error (`Either scheme or objectId must be given`); + + if (scheme !== undefined && objectId !== undefined) + throw new Error (`Both scheme, and objectId cannot be given`); + + if (objectId !== undefined && Object.entries (idArgs).length > 0) + throw new Error (`Both objectId, and idArgs cannot be given`); + + if (Object.entries (idArgs).length == 0) { + // We want to generate a new id, only if it doesn't have other + // attributes that it can use to uniquely identify. + // Eg, we don't do this for `dotnet:valuetype:{containerId:4, fieldOffset: 24}` + idArgs.num = this._next_id (); + } - _get_updated_ptr_id: function (objectId, new_args) { - var old_args = {}; - if (typeof (objectId) === 'string' && objectId.length) - old_args = this._get_ptr_args (objectId); + let idStr; + if (objectId !== undefined) { + idStr = objectId; + const old_props = this._id_table [idStr]; + if (old_props === undefined) + throw new Error (`ObjectId not found in the id table: ${idStr}`); - return `dotnet:pointer:${JSON.stringify ( Object.assign (old_args, new_args) )}`; + this._id_table [idStr] = Object.assign (old_props, props); + } else { + idStr = `dotnet:${scheme}:${JSON.stringify (idArgs)}`; + this._id_table [idStr] = props; + } + + return idStr; + }, + + /** + * @param {string} objectId + * @returns {object} + */ + _get_id_props: function (objectId) { + return this._id_table [objectId]; }, _get_deref_ptr_value: function (objectId) { - if (!this.mono_wasm_get_deref_ptr_value_info) - this.mono_wasm_get_deref_ptr_value_info = Module.cwrap("mono_wasm_get_deref_ptr_value", null, ['number', 'number']); + const ptr_args = this._get_id_props (objectId); + if (ptr_args === undefined) + throw new Error (`Unknown pointer id: ${objectId}`); - var ptr_args = this._get_ptr_args (objectId); if (ptr_args.ptr_addr == 0 || ptr_args.klass_addr == 0) throw new Error (`Both ptr_addr and klass_addr need to be non-zero, to dereference a pointer. objectId: ${objectId}`); - this.var_info = []; - var value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true); - this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr); + const value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true); + let { res_ok, res } = this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr); + if (!res_ok) + throw new Error (`Failed to dereference pointer ${objectId}`); - var res = MONO._fixup_name_value_objects(this.var_info); if (res.length > 0) { if (ptr_args.varName === undefined) throw new Error (`Bug: no varName found for the pointer. objectId: ${objectId}`); @@ -450,34 +834,25 @@ var MonoSupportLib = { } res = this._post_process_details (res); - this.var_info = []; return res; }, mono_wasm_get_details: function (objectId, args) { - var parts = objectId.split(":"); - if (parts[0] != "dotnet") - throw new Error ("Can't handle non-dotnet object ids. ObjectId: " + objectId); + let id = this._parse_object_id (objectId, true); - switch (parts[1]) { - case "object": - if (parts.length != 3) - throw new Error(`exception this time: Invalid object id format: ${objectId}`); + switch (id.scheme) { + case "object": { + if (isNaN (id.value)) + throw new Error (`Invalid objectId: ${objectId}. Expected a numeric id.`); - return this._post_process_details(this.mono_wasm_get_object_properties(parts[2], false)); + return this._get_object_properties(id.value, false); + } case "array": - return this._get_array_details(objectId, parts); + return this._get_array_values (id); case "valuetype": - if (parts.length != 3 && parts.length != 4) { - // dotnet:valuetype:vtid - // dotnet:valuetype:containerObjectId:vtId - throw new Error(`Invalid object id format: ${objectId}`); - } - - var containerObjectId = parts[2]; - return this._get_details_for_value_type(objectId, () => this.mono_wasm_get_object_properties(containerObjectId, true)); + return this._get_vt_properties(id); case "cfo_res": return this._get_cfo_res_details (objectId, args); @@ -492,7 +867,7 @@ var MonoSupportLib = { }, _cache_call_function_res: function (obj) { - var id = `dotnet:cfo_res:${this._next_call_function_res_id++}`; + const id = `dotnet:cfo_res:${this._next_call_function_res_id++}`; this._call_function_res_cache[id] = obj; return id; }, @@ -502,44 +877,69 @@ var MonoSupportLib = { delete this._cache_call_function_res[objectId]; }, - _invoke_getter_on_object: function (objectId, name) { - if (!this.mono_wasm_invoke_getter_on_object) - this.mono_wasm_invoke_getter_on_object = Module.cwrap ("mono_wasm_invoke_getter_on_object", 'void', [ 'number', 'string' ]); - - if (objectId < 0) { - // invalid id - return []; + /** + * @param {string} objectIdStr objectId + * @param {string} name property name + * @returns {object} return value + */ + _invoke_getter: function (objectIdStr, name) { + const id = this._parse_object_id (objectIdStr); + if (id === undefined) + throw new Error (`Invalid object id: ${objectIdStr}`); + + let getter_res; + if (id.scheme == 'object') { + if (isNaN (id.o) || id.o < 0) + throw new Error (`Invalid object id: ${objectIdStr}`); + + let { res_ok, res } = this.mono_wasm_invoke_getter_on_object_info (id.o, name); + if (!res_ok) + throw new Error (`Invoking getter on ${objectIdStr} failed`); + + getter_res = res; + } else if (id.scheme == 'valuetype') { + const id_props = this._get_id_props (objectIdStr); + if (id_props === undefined) + throw new Error (`Unknown valuetype id: ${objectIdStr}`); + + if (typeof id_props.value64 !== 'string' || isNaN (id_props.klass)) + throw new Error (`Bug: Cannot invoke getter on ${objectIdStr}, because of missing or invalid klass/value64 fields. idProps: ${JSON.stringify (id_props)}`); + + const dataPtr = Module._malloc (id_props.value64.length); + const dataHeap = new Uint8Array (Module.HEAPU8.buffer, dataPtr, id_props.value64.length); + dataHeap.set (new Uint8Array (this._base64_to_uint8 (id_props.value64))); + + let { res_ok, res } = this.mono_wasm_invoke_getter_on_value_info (dataHeap.byteOffset, id_props.klass, name); + Module._free (dataHeap.byteOffset); + + if (!res_ok) { + console.debug (`Invoking getter on valuetype ${objectIdStr}, with props: ${JSON.stringify (id_props)} failed`); + throw new Error (`Invoking getter on valuetype ${objectIdStr} failed`); + } + getter_res = res; + } else { + throw new Error (`Only object, and valuetypes supported for getters, id: ${objectIdStr}`); } - this.mono_wasm_invoke_getter_on_object (objectId, name); - var getter_res = MONO._post_process_details (MONO.var_info); - - MONO.var_info = []; - return getter_res [0]; + getter_res = MONO._post_process_details (getter_res); + return getter_res.length > 0 ? getter_res [0] : {}; }, _create_proxy_from_object_id: function (objectId) { - var details = this.mono_wasm_get_details(objectId); + const details = this.mono_wasm_get_details(objectId); - if (this._is_object_id_array (objectId)) + if (objectId.startsWith ('dotnet:array:')) return details.map (p => p.value); - var objIdParts = objectId.split (':'); - var objIdNum = -1; - if (objectId.startsWith ("dotnet:object:")) - objIdNum = objIdParts [2]; - - var proxy = {}; + let proxy = {}; Object.keys (details).forEach (p => { var prop = details [p]; if (prop.get !== undefined) { // TODO: `set` - // We don't add a `get` for non-object types right now, - // so, we shouldn't get here with objIdNum==-1 Object.defineProperty (proxy, prop.name, - { get () { return MONO._invoke_getter_on_object (objIdNum, prop.name); } } + { get () { return MONO._invoke_getter (objectId, prop.name); } } ); } else { proxy [prop.name] = prop.value; @@ -553,21 +953,27 @@ var MonoSupportLib = { if (request.arguments != undefined && !Array.isArray (request.arguments)) throw new Error (`"arguments" should be an array, but was ${request.arguments}`); - var objId = request.objectId; - var proxy; + const objId = request.objectId; + let proxy; - if (objId in this._call_function_res_cache) { - proxy = this._call_function_res_cache [objId]; - } else if (!objId.startsWith ('dotnet:cfo_res:')) { + if (objId.startsWith ('dotnet:cfo_res:')) { + if (objId in this._call_function_res_cache) + proxy = this._call_function_res_cache [objId]; + else + throw new Error (`Unknown object id ${objId}`); + } else { proxy = this._create_proxy_from_object_id (objId); } - var fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; - var fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`; + const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; + const fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`; - var fn_res = eval (fn_eval_str); - if (fn_res == undefined) // should we just return undefined? - throw Error ('Function returned undefined result'); + const fn_res = eval (fn_eval_str); + if (fn_res === undefined) + return { type: "undefined" }; + + if (fn_res === null || (fn_res.subtype === 'null' && fn_res.value === undefined)) + return fn_res; // primitive type if (Object (fn_res) !== fn_res) @@ -580,7 +986,7 @@ var MonoSupportLib = { if (request.returnByValue) return {type: "object", value: fn_res}; - var fn_res_id = this._cache_call_function_res (fn_res); + const fn_res_id = this._cache_call_function_res (fn_res); if (Object.getPrototypeOf (fn_res) == Array.prototype) { return { type: "object", @@ -595,8 +1001,8 @@ var MonoSupportLib = { }, _clear_per_step_state: function () { - this._next_value_type_id_var = 0; - this._value_types_cache = {}; + this._next_id_var = 0; + this._id_table = {}; }, mono_wasm_debugger_resume: function () { @@ -604,7 +1010,7 @@ var MonoSupportLib = { }, mono_wasm_start_single_stepping: function (kind) { - console.log (">> mono_wasm_start_single_stepping " + kind); + console.debug (">> mono_wasm_start_single_stepping " + kind); if (!this.mono_wasm_setup_single_step) this.mono_wasm_setup_single_step = Module.cwrap ("mono_wasm_setup_single_step", 'number', [ 'number']); @@ -613,16 +1019,75 @@ var MonoSupportLib = { return this.mono_wasm_setup_single_step (kind); }, + mono_wasm_set_pause_on_exceptions: function (state) { + if (!this.mono_wasm_pause_on_exceptions) + this.mono_wasm_pause_on_exceptions = Module.cwrap ("mono_wasm_pause_on_exceptions", 'number', [ 'number']); + var state_enum = 0; + switch (state) { + case 'uncaught': + state_enum = 1; //EXCEPTION_MODE_UNCAUGHT + break; + case 'all': + state_enum = 2; //EXCEPTION_MODE_ALL + break; + } + return this.mono_wasm_pause_on_exceptions (state_enum); + }, + + _register_c_fn: function (name, ...args) { + Object.defineProperty (this._c_fn_table, name + '_wrapper', { value: Module.cwrap (name, ...args) }); + }, + + /** + * Calls `Module.cwrap` for the function name, + * and creates a wrapper around it that returns + * `{ bool result, object var_info } + * + * @param {string} name C function name + * @param {string} ret_type + * @param {string[]} params + * + * @returns {void} + */ + _register_c_var_fn: function (name, ret_type, params) { + if (ret_type !== 'bool') + throw new Error (`Bug: Expected a C function signature that returns bool`); + + this._register_c_fn (name, ret_type, params); + Object.defineProperty (this, name + '_info', { + value: function (...args) { + MONO.var_info = []; + const res_ok = MONO._c_fn_table [name + '_wrapper'] (...args); + let res = MONO.var_info; + MONO.var_info = []; + if (res_ok) { + res = this._fixup_name_value_objects (res); + return { res_ok, res }; + } + + return { res_ok, res: undefined }; + } + }); + }, + mono_wasm_runtime_ready: function () { this.mono_wasm_runtime_is_ready = true; // DO NOT REMOVE - magic debugger init function - console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28", JSON.stringify (this.loaded_files)); + console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); this._clear_per_step_state (); // FIXME: where should this go? this._next_call_function_res_id = 0; this._call_function_res_cache = {}; + + this._c_fn_table = {}; + this._register_c_var_fn ('mono_wasm_get_object_properties', 'bool', [ 'number', 'bool' ]); + this._register_c_var_fn ('mono_wasm_get_array_values', 'bool', [ 'number', 'number', 'number', 'bool' ]); + this._register_c_var_fn ('mono_wasm_invoke_getter_on_object', 'bool', [ 'number', 'string' ]); + this._register_c_var_fn ('mono_wasm_invoke_getter_on_value', 'bool', [ 'number', 'number', 'string' ]); + this._register_c_var_fn ('mono_wasm_get_local_vars', 'bool', [ 'number', 'number', 'number']); + this._register_c_var_fn ('mono_wasm_get_deref_ptr_value', 'bool', [ 'number', 'number']); }, mono_wasm_set_breakpoint: function (assembly, method_token, il_offset) { @@ -640,7 +1105,7 @@ var MonoSupportLib = { }, // Set environment variable NAME to VALUE - // Should be called before mono_load_runtime_and_bcl () in most cases + // Should be called before mono_load_runtime_and_bcl () in most cases mono_wasm_setenv: function (name, value) { if (!this.wasm_setenv) this.wasm_setenv = Module.cwrap ('mono_wasm_setenv', null, ['string', 'string']); @@ -652,7 +1117,7 @@ var MonoSupportLib = { this.wasm_parse_runtime_options = Module.cwrap ('mono_wasm_parse_runtime_options', null, ['number', 'number']); var argv = Module._malloc (options.length * 4); var wasm_strdup = Module.cwrap ('mono_wasm_strdup', 'number', ['string']); - aindex = 0; + let aindex = 0; for (var i = 0; i < options.length; ++i) { Module.setValue (argv + (aindex * 4), wasm_strdup (options [i]), "i32"); aindex += 1; @@ -696,117 +1161,440 @@ var MonoSupportLib = { Module.ccall ('mono_wasm_load_profiler_coverage', null, ['string'], [arg]); }, - mono_load_runtime_and_bcl: function (vfs_prefix, deploy_prefix, enable_debugging, file_list, loaded_cb, fetch_file_cb) { - var pending = file_list.length; - var loaded_files = []; - var loaded_files_with_debug_info = []; - var mono_wasm_add_assembly = Module.cwrap ('mono_wasm_add_assembly', 'number', ['string', 'number', 'number']); - - if (!fetch_file_cb) { - if (ENVIRONMENT_IS_NODE) { - var fs = require('fs'); - fetch_file_cb = function (asset) { - console.log("MONO_WASM: Loading... " + asset); - var binary = fs.readFileSync (asset); - var resolve_func2 = function(resolve, reject) { - resolve(new Uint8Array (binary)); - }; + _apply_configuration_from_args: function (args) { + for (var k in (args.environment_variables || {})) + MONO.mono_wasm_setenv (k, args.environment_variables[k]); - var resolve_func1 = function(resolve, reject) { - var response = { - ok: true, - url: asset, - arrayBuffer: function() { - return new Promise(resolve_func2); - } - }; - resolve(response); - }; + if (args.runtime_options) + MONO.mono_wasm_set_runtime_options (args.runtime_options); + + if (args.aot_profiler_options) + MONO.mono_wasm_init_aot_profiler (args.aot_profiler_options); - return new Promise(resolve_func1); + if (args.coverage_profiler_options) + MONO.mono_wasm_init_coverage_profiler (args.coverage_profiler_options); + }, + + _get_fetch_file_cb_from_args: function (args) { + if (typeof (args.fetch_file_cb) === "function") + return args.fetch_file_cb; + + if (ENVIRONMENT_IS_NODE) { + var fs = require('fs'); + return function (asset) { + console.debug ("MONO_WASM: Loading... " + asset); + var binary = fs.readFileSync (asset); + var resolve_func2 = function (resolve, reject) { + resolve (new Uint8Array (binary)); }; - } else { - fetch_file_cb = function (asset) { - return fetch (asset, { credentials: 'same-origin' }); + + var resolve_func1 = function (resolve, reject) { + var response = { + ok: true, + url: asset, + arrayBuffer: function () { + return new Promise (resolve_func2); + } + }; + resolve (response); + }; + + return new Promise (resolve_func1); + }; + } else if (typeof (fetch) === "function") { + return function (asset) { + return fetch (asset, { credentials: 'same-origin' }); + }; + } else { + throw new Error ("No fetch_file_cb was provided and this environment does not expose 'fetch'."); + } + }, + + _handle_loaded_asset: function (ctx, asset, url, blob) { + var bytes = new Uint8Array (blob); + if (ctx.tracing) + console.log ("MONO_WASM: Loaded:", asset.name, "size", bytes.length, "from", url); + + var virtualName = asset.virtual_path || asset.name; + var offset = null; + + switch (asset.behavior) { + case "assembly": + ctx.loaded_files.push ({ url: url, file: virtualName}); + case "heap": + case "icu": + offset = this.mono_wasm_load_bytes_into_heap (bytes); + ctx.loaded_assets[virtualName] = [offset, bytes.length]; + break; + + case "vfs": + // FIXME + var lastSlash = virtualName.lastIndexOf("/"); + var parentDirectory = (lastSlash > 0) + ? virtualName.substr(0, lastSlash) + : null; + var fileName = (lastSlash > 0) + ? virtualName.substr(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) + fileName = fileName.substr(1); + if (parentDirectory) { + if (ctx.tracing) + console.log ("MONO_WASM: Creating directory '" + parentDirectory + "'"); + + var pathRet = ctx.createPath( + "/", parentDirectory, true, true // fixme: should canWrite be false? + ); + } else { + parentDirectory = "/"; } + + if (ctx.tracing) + console.log ("MONO_WASM: Creating file '" + fileName + "' in directory '" + parentDirectory + "'"); + + if (!this.mono_wasm_load_data_archive (bytes, parentDirectory)) { + var fileRet = ctx.createDataFile ( + parentDirectory, fileName, + bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); + } + break; + + default: + throw new Error ("Unrecognized asset behavior:", asset.behavior, "for asset", asset.name); + } + + if (asset.behavior === "assembly") { + var hasPpdb = ctx.mono_wasm_add_assembly (virtualName, offset, bytes.length); + + if (!hasPpdb) { + var index = ctx.loaded_files.findIndex(element => element.file == virtualName); + ctx.loaded_files.splice(index, 1); } } + else if (asset.behavior === "icu") { + if (this.mono_wasm_load_icu_data (offset)) + ctx.num_icu_assets_loaded_successfully += 1; + else + console.error ("Error loading ICU asset", asset.name); + } + }, - file_list.forEach (function(file_name) { - - var fetch_promise = fetch_file_cb (locateFile(deploy_prefix + "/" + file_name)); + // deprecated + mono_load_runtime_and_bcl: function ( + unused_vfs_prefix, deploy_prefix, debug_level, file_list, loaded_cb, fetch_file_cb + ) { + var args = { + fetch_file_cb: fetch_file_cb, + loaded_cb: loaded_cb, + debug_level: debug_level, + assembly_root: deploy_prefix, + assets: [] + }; + + for (var i = 0; i < file_list.length; i++) { + var file_name = file_list[i]; + var behavior; + if (file_name.startsWith ("icudt") && file_name.endsWith (".dat")) { + // ICU data files are expected to be "icudt%FilterName%.dat" + behavior = "icu"; + } else { // if (file_name.endsWith (".pdb") || file_name.endsWith (".dll")) + behavior = "assembly"; + } + + args.assets.push ({ + name: file_name, + behavior: behavior + }); + } + + return this.mono_load_runtime_and_bcl_args (args); + }, + + // Initializes the runtime and loads assemblies, debug information, and other files. + // @args is a dictionary-style Object with the following properties: + // assembly_root: (required) the subfolder containing managed assemblies and pdbs + // debug_level or enable_debugging: (required) + // assets: (required) a list of assets to load along with the runtime. each asset + // is a dictionary-style Object with the following properties: + // name: (required) the name of the asset, including extension. + // behavior: (required) determines how the asset will be handled once loaded: + // "heap": store asset into the native heap + // "assembly": load asset as a managed assembly (or debugging information) + // "icu": load asset as an ICU data archive + // "vfs": load asset into the virtual filesystem (for fopen, File.Open, etc) + // load_remote: (optional) if true, an attempt will be made to load the asset + // from each location in @args.remote_sources. + // virtual_path: (optional) if specified, overrides the path of the asset in + // the virtual filesystem and similar data structures once loaded. + // is_optional: (optional) if true, any failure to load this asset will be ignored. + // loaded_cb: (required) a function () invoked when loading has completed. + // fetch_file_cb: (optional) a function (string) invoked to fetch a given file. + // If no callback is provided a default implementation appropriate for the current + // environment will be selected (readFileSync in node, fetch elsewhere). + // If no default implementation is available this call will fail. + // remote_sources: (optional) additional search locations for assets. + // sources will be checked in sequential order until the asset is found. + // the string "./" indicates to load from the application directory (as with the + // files in assembly_list), and a fully-qualified URL like "https://example.com/" indicates + // that asset loads can be attempted from a remote server. Sources must end with a "/". + // environment_variables: (optional) dictionary-style Object containing environment variables + // runtime_options: (optional) array of runtime options as strings + // aot_profiler_options: (optional) dictionary-style Object. see the comments for + // mono_wasm_init_aot_profiler. If omitted, aot profiler will not be initialized. + // coverage_profiler_options: (optional) dictionary-style Object. see the comments for + // mono_wasm_init_coverage_profiler. If omitted, coverage profiler will not be initialized. + // globalization_mode: (optional) configures the runtime's globalization mode: + // "icu": load ICU globalization data from any runtime assets with behavior "icu". + // "invariant": operate in invariant globalization mode. + // "auto" (default): if "icu" behavior assets are present, use ICU, otherwise invariant. + // diagnostic_tracing: (optional) enables diagnostic log messages during startup + mono_load_runtime_and_bcl_args: function (args) { + try { + return this._load_assets_and_runtime (args); + } catch (exc) { + console.error ("error in mono_load_runtime_and_bcl_args:", exc); + throw exc; + } + }, + + // @bytes must be a typed array. space is allocated for it in the native heap + // and it is copied to that location. returns the address of the allocation. + mono_wasm_load_bytes_into_heap: function (bytes) { + var memoryOffset = Module._malloc (bytes.length); + var heapBytes = new Uint8Array (Module.HEAPU8.buffer, memoryOffset, bytes.length); + heapBytes.set (bytes); + return memoryOffset; + }, + + num_icu_assets_loaded_successfully: 0, + + // @offset must be the address of an ICU data archive in the native heap. + // returns true on success. + mono_wasm_load_icu_data: function (offset) { + var fn = Module.cwrap ('mono_wasm_load_icu_data', 'number', ['number']); + var ok = (fn (offset)) === 1; + if (ok) + this.num_icu_assets_loaded_successfully++; + return ok; + }, + + _finalize_startup: function (args, ctx) { + var loaded_files_with_debug_info = []; + + MONO.loaded_assets = ctx.loaded_assets; + ctx.loaded_files.forEach(value => loaded_files_with_debug_info.push(value.url)); + MONO.loaded_files = loaded_files_with_debug_info; + if (ctx.tracing) { + console.log ("MONO_WASM: loaded_assets: " + JSON.stringify(ctx.loaded_assets)); + console.log ("MONO_WASM: loaded_files: " + JSON.stringify(ctx.loaded_files)); + } + + var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); - fetch_promise.then (function (response) { + console.debug ("MONO_WASM: Initializing mono runtime"); + + this.mono_wasm_globalization_init (args.globalization_mode); + + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + try { + load_runtime ("unused", args.debug_level); + } catch (ex) { + print ("MONO_WASM: load_runtime () failed: " + ex); + print ("MONO_WASM: Stacktrace: \n"); + print (ex.stack); + + var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); + wasm_exit (1); + } + } else { + load_runtime ("unused", args.debug_level); + } + + MONO.mono_wasm_runtime_ready (); + args.loaded_cb (); + }, + + _load_assets_and_runtime: function (args) { + if (args.enable_debugging) + args.debug_level = args.enable_debugging; + if (args.assembly_list) + throw new Error ("Invalid args (assembly_list was replaced by assets)"); + if (args.runtime_assets) + throw new Error ("Invalid args (runtime_assets was replaced by assets)"); + if (args.runtime_asset_sources) + throw new Error ("Invalid args (runtime_asset_sources was replaced by remote_sources)"); + if (!args.loaded_cb) + throw new Error ("loaded_cb not provided"); + + var ctx = { + tracing: args.diagnostic_tracing || false, + pending_count: args.assets.length, + mono_wasm_add_assembly: Module.cwrap ('mono_wasm_add_assembly', 'number', ['string', 'number', 'number']), + loaded_assets: Object.create (null), + // dlls and pdbs, used by blazor and the debugger + loaded_files: [], + createPath: Module['FS_createPath'], + createDataFile: Module['FS_createDataFile'] + }; + + if (ctx.tracing) + console.log ("mono_wasm_load_runtime_with_args", JSON.stringify(args)); + + this._apply_configuration_from_args (args); + + var fetch_file_cb = this._get_fetch_file_cb_from_args (args); + + var onPendingRequestComplete = function () { + --ctx.pending_count; + + if (ctx.pending_count === 0) { + try { + MONO._finalize_startup (args, ctx); + } catch (exc) { + console.error ("Unhandled exception in _finalize_startup", exc); + throw exc; + } + } + }; + + var processFetchResponseBuffer = function (asset, url, blob) { + try { + MONO._handle_loaded_asset (ctx, asset, url, blob); + } catch (exc) { + console.error ("Unhandled exception in processFetchResponseBuffer", exc); + throw exc; + } finally { + onPendingRequestComplete (); + } + }; + + args.assets.forEach (function (asset) { + var attemptNextSource; + var sourceIndex = 0; + var sourcesList = asset.load_remote ? args.remote_sources : [""]; + + var handleFetchResponse = function (response) { if (!response.ok) { - // If it's a 404 on a .pdb, we don't want to block the app from starting up. - // We'll just skip that file and continue (though the 404 is logged in the console). - if (response.status === 404 && file_name.match(/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors) { - --pending; - throw "MONO-WASM: Skipping failed load for .pdb file: '" + file_name + "'"; - } - else { - throw "MONO_WASM: Failed to load file: '" + file_name + "'"; + try { + attemptNextSource (); + return; + } catch (exc) { + console.error ("MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset", asset.name, exc); + throw exc; } } - else { - loaded_files.push ({ url: response.url, file: file_name}); - return response ['arrayBuffer'] (); - } - }).then (function (blob) { - var asm = new Uint8Array (blob); - var memory = Module._malloc(asm.length); - var heapBytes = new Uint8Array(Module.HEAPU8.buffer, memory, asm.length); - heapBytes.set (asm); - var hasPpdb = mono_wasm_add_assembly (file_name, memory, asm.length); - - if (!hasPpdb) { - var index = loaded_files.findIndex(element => element.file == file_name); - loaded_files.splice(index, 1); + + try { + var bufferPromise = response ['arrayBuffer'] (); + bufferPromise.then (processFetchResponseBuffer.bind (this, asset, response.url)); + } catch (exc) { + console.error ("MONO_WASM: Unhandled exception in handleFetchResponse for asset", asset.name, exc); + attemptNextSource (); } - //console.log ("MONO_WASM: Loaded: " + file_name); - --pending; - if (pending == 0) { - loaded_files.forEach(value => loaded_files_with_debug_info.push(value.url)); - MONO.loaded_files = loaded_files_with_debug_info; - var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); - - console.log ("MONO_WASM: Initializing mono runtime"); - if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { - try { - load_runtime (vfs_prefix, enable_debugging); - } catch (ex) { - print ("MONO_WASM: load_runtime () failed: " + ex); - print ("MONO_WASM: Stacktrace: \n"); - print (ex.stack); - - var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); - wasm_exit (1); + }; + + attemptNextSource = function () { + if (sourceIndex >= sourcesList.length) { + var msg = "MONO_WASM: Failed to load " + asset.name; + try { + var isOk = asset.is_optional || + (asset.name.match (/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors); + + if (isOk) + console.debug (msg); + else { + console.error (msg); + throw new Error (msg); } + } finally { + onPendingRequestComplete (); + } + } + + var sourcePrefix = sourcesList[sourceIndex]; + sourceIndex++; + + // HACK: Special-case because MSBuild doesn't allow "" as an attribute + if (sourcePrefix === "./") + sourcePrefix = ""; + + var attemptUrl; + if (sourcePrefix.trim() === "") { + if (asset.behavior === "assembly") + attemptUrl = locateFile (args.assembly_root + "/" + asset.name); + else + attemptUrl = asset.name; + } else { + attemptUrl = sourcePrefix + asset.name; + } + + try { + if (asset.name === attemptUrl) { + if (ctx.tracing) + console.log ("Attempting to fetch '" + attemptUrl + "'"); } else { - load_runtime (vfs_prefix, enable_debugging); + if (ctx.tracing) + console.log ("Attempting to fetch '" + attemptUrl + "' for", asset.name); } - MONO.mono_wasm_runtime_ready (); - loaded_cb (); + var fetch_promise = fetch_file_cb (attemptUrl); + fetch_promise.then (handleFetchResponse); + } catch (exc) { + console.error ("MONO_WASM: Error fetching " + attemptUrl, exc); + attemptNextSource (); } - }); + }; + + attemptNextSource (); }); }, + // Performs setup for globalization. + // @globalization_mode is one of "icu", "invariant", or "auto". + // "auto" will use "icu" if any ICU data archives have been loaded, + // otherwise "invariant". + mono_wasm_globalization_init: function (globalization_mode) { + var invariantMode = false; + + if (globalization_mode === "invariant") + invariantMode = true; + + if (!invariantMode) { + if (this.num_icu_assets_loaded_successfully > 0) { + console.debug ("MONO_WASM: ICU data archive(s) loaded, disabling invariant mode"); + } else if (globalization_mode !== "icu") { + console.debug ("MONO_WASM: ICU data archive(s) not loaded, using invariant globalization mode"); + invariantMode = true; + } else { + var msg = "invariant globalization mode is inactive and no ICU data archives were loaded"; + console.error ("MONO_WASM: ERROR: " + msg); + throw new Error (msg); + } + } + + if (invariantMode) + this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1"); + }, + + // Used by the debugger to enumerate loaded dlls and pdbs mono_wasm_get_loaded_files: function() { - console.log(">>>mono_wasm_get_loaded_files"); - return this.loaded_files; + return MONO.loaded_files; }, - + + mono_wasm_get_loaded_asset_table: function() { + return MONO.loaded_assets; + }, + mono_wasm_clear_all_breakpoints: function() { if (!this.mono_clear_bps) this.mono_clear_bps = Module.cwrap ('mono_wasm_clear_all_breakpoints', null); this.mono_clear_bps (); }, - + mono_wasm_add_null_var: function(className) { - fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + let fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); if (!fixed_class_name) { // Eg, when a @className is passed from js itself, like // mono_wasm_add_null_var ("string") @@ -830,12 +1618,13 @@ var MonoSupportLib = { value: { type: "string", value: var_value, + description: var_value } }); }, _mono_wasm_add_getter_var: function(className, invokable) { - fixed_class_name = MONO._mono_csharp_fixup_class_name (className); + const fixed_class_name = MONO._mono_csharp_fixup_class_name (className); if (invokable != 0) { var name; if (MONO.var_info.length > 0) @@ -862,7 +1651,7 @@ var MonoSupportLib = { }, _mono_wasm_add_array_var: function(className, objectId, length) { - fixed_class_name = MONO._mono_csharp_fixup_class_name(className); + const fixed_class_name = MONO._mono_csharp_fixup_class_name(className); if (objectId == 0) { MONO.mono_wasm_add_null_var (fixed_class_name); return; @@ -874,42 +1663,121 @@ var MonoSupportLib = { subtype: "array", className: fixed_class_name, description: `${fixed_class_name}(${length})`, - objectId: "dotnet:array:"+ objectId, + objectId: this._new_or_add_id_props ({ scheme: 'array', idArgs: { arrayId: objectId } }) + } + }); + }, + + // FIXME: improve + _base64_to_uint8: function (base64String) { + const byteCharacters = atob (base64String); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + + return new Uint8Array (byteNumbers); + }, + + _begin_value_type_var: function(className, args) { + if (args === undefined || (typeof args !== 'object')) { + console.debug (`_begin_value_type_var: Expected an args object`); + return; + } + + const fixed_class_name = MONO._mono_csharp_fixup_class_name(className); + const toString = args.toString; + const base64String = btoa (String.fromCharCode (...new Uint8Array (Module.HEAPU8.buffer, args.value_addr, args.value_size))); + const vt_obj = { + value: { + type : "object", + className : fixed_class_name, + description : (toString == 0 ? fixed_class_name: Module.UTF8ToString (toString)), + expanded : true, + isValueType : true, + __extra_vt_props: { klass: args.klass, value64: base64String }, + members : [] + } + }; + if (MONO._vt_stack.length == 0) + MONO._old_var_info = MONO.var_info; + + MONO.var_info = vt_obj.value.members; + MONO._vt_stack.push (vt_obj); + }, + + _end_value_type_var: function() { + let top_vt_obj_popped = MONO._vt_stack.pop (); + top_vt_obj_popped.value.members = MONO._filter_automatic_properties ( + MONO._fixup_name_value_objects (top_vt_obj_popped.value.members)); + + if (MONO._vt_stack.length == 0) { + MONO.var_info = MONO._old_var_info; + MONO.var_info.push(top_vt_obj_popped); + } else { + var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1]; + top_obj.value.members.push (top_vt_obj_popped); + MONO.var_info = top_obj.value.members; + } + }, + + _add_valuetype_unexpanded_var: function(className, args) { + if (args === undefined || (typeof args !== 'object')) { + console.debug (`_add_valuetype_unexpanded_var: Expected an args object`); + return; + } + + const fixed_class_name = MONO._mono_csharp_fixup_class_name (className); + const toString = args.toString; + + MONO.var_info.push ({ + value: { + type: "object", + className: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), + isValueType: true } }); }, + mono_wasm_add_typed_value: function (type, str_value, value) { - var type_str = type; + let type_str = type; if (typeof type != 'string') type_str = Module.UTF8ToString (type); - if (typeof str_value != 'string') str_value = Module.UTF8ToString (str_value); switch (type_str) { - case "bool": + case "bool": { + const v = value != 0; MONO.var_info.push ({ value: { type: "boolean", - value: value != 0 + value: v, + description: v.toString () } }); break; + } - case "char": + case "char": { + const v = `${value} '${String.fromCharCode (value)}'`; MONO.var_info.push ({ value: { type: "symbol", - value: `${value} '${String.fromCharCode (value)}'` + value: v, + description: v } }); break; + } case "number": MONO.var_info.push ({ value: { type: "number", - value: value + value: value, + description: '' + value } }); break; @@ -926,8 +1794,20 @@ var MonoSupportLib = { MONO._mono_wasm_add_array_var (str_value, value.objectId, value.length); break; + case "begin_vt": + MONO._begin_value_type_var (str_value, value); + break; + + case "end_vt": + MONO._end_value_type_var (); + break; + + case "unexpanded_vt": + MONO._add_valuetype_unexpanded_var (str_value, value); + break; + case "pointer": { - var fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value); + const fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value); if (value.klass_addr == 0 || value.ptr_addr == 0 || fixed_value_str.startsWith ('(void*')) { // null or void*, which we can't deref MONO.var_info.push({ @@ -943,7 +1823,7 @@ var MonoSupportLib = { type: "object", className: fixed_value_str, description: fixed_value_str, - objectId: this._get_updated_ptr_id ('', value) + objectId: this._new_or_add_id_props ({ scheme: 'pointer', props: value }) } }); } @@ -951,7 +1831,7 @@ var MonoSupportLib = { break; default: { - var msg = `'${str_value}' ${value}`; + const msg = `'${str_value}' ${value}`; MONO.var_info.push ({ value: { @@ -971,6 +1851,59 @@ var MonoSupportLib = { // and nested class names like Foo/Bar to Foo.Bar return className.replace(/\//g, '.').replace(/`\d+/g, ''); }, + + mono_wasm_load_data_archive: function (data, prefix) { + if (data.length < 8) + return false; + + var dataview = new DataView(data.buffer); + var magic = dataview.getUint32(0, true); + // get magic number + if (magic != 0x626c6174) { + return false; + } + var manifestSize = dataview.getUint32(4, true); + if (manifestSize == 0 || data.length < manifestSize + 8) + return false; + + var manifest; + try { + manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + manifest = JSON.parse(manifestContent); + if (!(manifest instanceof Array)) + return false; + } catch (exc) { + return false; + } + + data = data.slice(manifestSize+8); + + // Create the folder structure + // /usr/share/zoneinfo + // /usr/share/zoneinfo/Africa + // /usr/share/zoneinfo/Asia + // .. + + var folders = new Set() + manifest.filter(m => { + var file = m[0]; + var last = file.lastIndexOf ("/"); + var directory = file.slice (0, last); + folders.add(directory); + }); + folders.forEach(folder => { + Module['FS_createPath'](prefix, folder, true, true); + }); + + for (row of manifest) { + var name = row[0]; + var length = row[1]; + var bytes = data.slice(0, length); + Module['FS_createDataFile'](prefix, name, bytes, true, true); + data = data.slice(length); + } + return true; + } }, mono_wasm_add_typed_value: function (type, str_value, value) { @@ -988,55 +1921,6 @@ var MonoSupportLib = { MONO._async_method_objectId = objectId; }, - mono_wasm_begin_value_type_var: function(className, toString) { - fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); - var vt_obj = { - value: { - type: "object", - className: fixed_class_name, - description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), - // objectId will be generated by MonoProxy - expanded: true, - isValueType: true, - members: [] - } - }; - if (MONO._vt_stack.length == 0) - MONO._old_var_info = MONO.var_info; - - MONO.var_info = vt_obj.value.members; - MONO._vt_stack.push (vt_obj); - }, - - mono_wasm_end_value_type_var: function() { - var top_vt_obj_popped = MONO._vt_stack.pop (); - top_vt_obj_popped.value.members = MONO._filter_automatic_properties ( - MONO._fixup_name_value_objects (top_vt_obj_popped.value.members)); - - if (MONO._vt_stack.length == 0) { - MONO.var_info = MONO._old_var_info; - MONO.var_info.push(top_vt_obj_popped); - } else { - var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1]; - top_obj.value.members.push (top_vt_obj_popped); - MONO.var_info = top_obj.value.members; - } - }, - - mono_wasm_add_value_type_unexpanded_var: function (className, toString) { - fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); - MONO.var_info.push({ - value: { - type: "object", - className: fixed_class_name, - description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), - // objectId added when enumerating object's properties - expanded: false, - isValueType: true - } - }); - }, - mono_wasm_add_enum_var: function(className, members, value) { // FIXME: flags // @@ -1044,13 +1928,13 @@ var MonoSupportLib = { // group0: Monday:0 // group1: Monday // group2: 0 - var re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g') - var members_str = Module.UTF8ToString (members); + const re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g') + const members_str = Module.UTF8ToString (members); - var match = re.exec(members_str); - var member_name = match == null ? ('' + value) : match [1]; + const match = re.exec(members_str); + const member_name = match == null ? ('' + value) : match [1]; - fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); MONO.var_info.push({ value: { type: "object", @@ -1073,7 +1957,7 @@ var MonoSupportLib = { return; } - fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); MONO.var_info.push({ value: { type: "object", @@ -1110,11 +1994,11 @@ var MonoSupportLib = { return `${ret_sig} ${method_name} (${args_sig})`; } - var tgt_sig; + let tgt_sig; if (targetName != 0) tgt_sig = args_to_sig (Module.UTF8ToString (targetName)); - var type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className)); + const type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className)); if (objectId == -1) { // Target property @@ -1183,9 +2067,19 @@ var MonoSupportLib = { }, mono_wasm_fire_bp: function () { - console.log ("mono_wasm_fire_bp"); + // eslint-disable-next-line no-debugger debugger; - } + }, + + mono_wasm_fire_exception: function (exception_id, message, class_name, uncaught) { + MONO.active_exception = { + exception_id: exception_id, + message : Module.UTF8ToString (message), + class_name : Module.UTF8ToString (class_name), + uncaught : uncaught + }; + debugger; + }, }; autoAddDeps(MonoSupportLib, '$MONO') diff --git a/sdks/wasm/tests/debugger/debugger-array-test.cs b/sdks/wasm/tests/debugger/debugger-array-test.cs index fb09fdeea9b1..282db743e882 100644 --- a/sdks/wasm/tests/debugger/debugger-array-test.cs +++ b/sdks/wasm/tests/debugger/debugger-array-test.cs @@ -1,285 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Threading.Tasks; namespace DebuggerTests { - public class ArrayTestsClass - { - public static void PrimitiveTypeLocals (bool call_other = false) - { - var int_arr = new int[] { 4, 70, 1 }; - var int_arr_empty = new int[0]; - int[] int_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"int_arr: {int_arr.Length}, {int_arr_empty.Length}, {int_arr_null?.Length}"); - } - - public static void ValueTypeLocals (bool call_other = false) - { - var point_arr = new Point[] { - new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Green }, - new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue }, - }; - - var point_arr_empty = new Point[0]; - Point[] point_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"point_arr: {point_arr.Length}, {point_arr_empty.Length}, {point_arr_null?.Length}"); - } - - public static void ObjectTypeLocals (bool call_other = false) - { - var class_arr = new SimpleClass [] { - new SimpleClass { X = 5, Y = -2, Id = "class_arr#Id#0", Color = RGB.Green }, - null, - new SimpleClass { X = 123, Y = 0, Id = "class_arr#Id#2", Color = RGB.Blue }, - }; - - var class_arr_empty = new SimpleClass [0]; - SimpleClass [] class_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"class_arr: {class_arr.Length}, {class_arr_empty.Length}, {class_arr_null?.Length}"); - } - - public static void GenericTypeLocals (bool call_other = false) - { - var gclass_arr = new GenericClass [] { - null, - new GenericClass { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 }, - new GenericClass { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 }, - }; - - var gclass_arr_empty = new GenericClass [0]; - GenericClass [] gclass_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"gclass_arr: {gclass_arr.Length}, {gclass_arr_empty.Length}, {gclass_arr_null?.Length}"); - } - - public static void GenericValueTypeLocals (bool call_other = false) - { - var gvclass_arr = new SimpleGenericStruct [] { - new SimpleGenericStruct { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } }, - new SimpleGenericStruct { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } } - }; - - var gvclass_arr_empty = new SimpleGenericStruct [0]; - SimpleGenericStruct [] gvclass_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); - } - - static void OtherMethod () - { - YetAnotherMethod (); - Console.WriteLine ($"Just a placeholder for breakpoints"); - } - - static void YetAnotherMethod () - { - Console.WriteLine ($"Just a placeholder for breakpoints"); - } - - public static void ObjectArrayMembers () - { - var c = new Container { - id = "c#id", - ClassArrayProperty = new SimpleClass[] { - new SimpleClass { X = 5, Y = -2, Id = "ClassArrayProperty#Id#0", Color = RGB.Green }, - new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayProperty#Id#1", Color = RGB.Green }, - null - }, - ClassArrayField = new SimpleClass[] { - null, - new SimpleClass { X = 5, Y = -2, Id = "ClassArrayField#Id#1", Color = RGB.Blue }, - new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayField#Id#2", Color = RGB.Green }, - }, - PointsProperty = new Point[] { - new Point { X = 5, Y = -2, Id = "PointsProperty#Id#0", Color = RGB.Green }, - new Point { X = 123, Y = 0, Id = "PointsProperty#Id#1", Color = RGB.Blue }, - }, - PointsField = new Point[] { - new Point { X = 5, Y = -2, Id = "PointsField#Id#0", Color = RGB.Green }, - new Point { X = 123, Y = 0, Id = "PointsField#Id#1", Color = RGB.Blue }, - } - }; - - Console.WriteLine ($"Back from PlaceholderMethod, {c.ClassArrayProperty?.Length}"); - c.PlaceholderMethod (); - Console.WriteLine ($"Back from PlaceholderMethod, {c.id}"); - } - - public static async Task ValueTypeLocalsAsync (bool call_other = false) - { - var gvclass_arr = new SimpleGenericStruct [] { - new SimpleGenericStruct { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } }, - new SimpleGenericStruct { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } } - }; - - var gvclass_arr_empty = new SimpleGenericStruct [0]; - SimpleGenericStruct [] gvclass_arr_null = null; - Console.WriteLine ($"ValueTypeLocalsAsync: call_other: {call_other}"); - SimpleGenericStruct gvclass; - Point[] points = null; - - if (call_other) { - (gvclass, points) = await new ArrayTestsClass ().InstanceMethodValueTypeLocalsAsync> (gvclass_arr [0]); - Console.WriteLine ($"* gvclass: {gvclass}, points: {points.Length}"); - } - - Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); - return true; - } - - public async Task<(T, Point[])> InstanceMethodValueTypeLocalsAsync (T t1) - { - var point_arr = new Point[] { - new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Red }, - new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue } - }; - var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green }; - - Console.WriteLine ($"point_arr: {point_arr.Length}, T: {t1}, point: {point}"); - return (t1, new Point[] {point_arr [0], point_arr [1], point}); - } - - // A workaround for method invocations on structs not working right now - public static async Task EntryPointForStructMethod (bool call_other = false) - { - await Point.AsyncMethod (call_other); - } - - public static void GenericValueTypeLocals2 (bool call_other = false) - { - var gvclass_arr = new SimpleGenericStruct [] { - new SimpleGenericStruct { - Id = "gvclass_arr#0#Id", Color = RGB.Red, - Value = new Point[] { - new Point { X = 100, Y = 200, Id = "gvclass_arr#0#0#Value#Id", Color = RGB.Red }, - new Point { X = 100, Y = 200, Id = "gvclass_arr#0#1#Value#Id", Color = RGB.Green } - } - }, - - new SimpleGenericStruct { - Id = "gvclass_arr#1#Id", Color = RGB.Blue, - Value = new Point[] { - new Point { X = 100, Y = 200, Id = "gvclass_arr#1#0#Value#Id", Color = RGB.Green }, - new Point { X = 100, Y = 200, Id = "gvclass_arr#1#1#Value#Id", Color = RGB.Blue } - } - }, - }; - - var gvclass_arr_empty = new SimpleGenericStruct [0]; - SimpleGenericStruct [] gvclass_arr_null = null; - - if (call_other) - OtherMethod (); - - Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); - } - } - - public class Container - { - public string id; - public SimpleClass[] ClassArrayProperty { get; set; } - public SimpleClass[] ClassArrayField; - - public Point[] PointsProperty { get; set; } - public Point[] PointsField; - - public void PlaceholderMethod () - { - Console.WriteLine ($"Container.PlaceholderMethod"); - } - } - - public struct Point - { - public int X, Y; - public string Id { get; set; } - public RGB Color { get; set; } - - /* instance too */ - public static async Task AsyncMethod (bool call_other) - { - int local_i = 5; - var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue }; - if (call_other) - await new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.AsyncInstanceMethod (sc); - Console.WriteLine ($"AsyncMethod local_i: {local_i}, sc: {sc.Id}"); - } - - public async Task AsyncInstanceMethod (SimpleClass sc_arg) - { - var local_gs = new SimpleGenericStruct { Id = "local_gs#Id", Color = RGB.Green, Value = 4 }; - sc_arg.Id = "sc_arg#Id"; - Console.WriteLine ($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); - } - - public void GenericInstanceMethod (T sc_arg) where T: SimpleClass - { - var local_gs = new SimpleGenericStruct { Id = "local_gs#Id", Color = RGB.Green, Value = 4 }; - sc_arg.Id = "sc_arg#Id"; - Console.WriteLine ($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); - } - } - - public class SimpleClass - { - public int X, Y; - public string Id { get; set; } - public RGB Color { get; set; } - - public Point PointWithCustomGetter { get { return new Point { X = 100, Y = 400, Id = "SimpleClass#Point#gen#Id", Color = RGB.Green }; } } - } - - public class GenericClass - { - public string Id { get; set; } - public RGB Color { get; set; } - public T Value { get; set; } - } - - public struct SimpleGenericStruct - { - public string Id { get; set; } - public RGB Color { get; set; } - public T Value { get; set; } - } - - public class EntryClass { - public static void run () - { - ArrayTestsClass.PrimitiveTypeLocals (true); - ArrayTestsClass.ValueTypeLocals (true); - ArrayTestsClass.ObjectTypeLocals (true); - - ArrayTestsClass.GenericTypeLocals (true); - ArrayTestsClass.GenericValueTypeLocals (true); - ArrayTestsClass.GenericValueTypeLocals2 (true); - - ArrayTestsClass.ObjectArrayMembers (); - - ArrayTestsClass.ValueTypeLocalsAsync (true).Wait (); - - ArrayTestsClass.EntryPointForStructMethod (true).Wait (); - - var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue }; - new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.GenericInstanceMethod (sc); - } - } + public class ArrayTestsClass + { + public static void PrimitiveTypeLocals(bool call_other = false) + { + var int_arr = new int[] { 4, 70, 1 }; + var int_arr_empty = new int[0]; + int[] int_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"int_arr: {int_arr.Length}, {int_arr_empty.Length}, {int_arr_null?.Length}"); + } + + public static void ValueTypeLocals(bool call_other = false) + { + var point_arr = new Point[] + { + new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Green }, + new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue }, + }; + + var point_arr_empty = new Point[0]; + Point[] point_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"point_arr: {point_arr.Length}, {point_arr_empty.Length}, {point_arr_null?.Length}"); + } + + public static void ObjectTypeLocals(bool call_other = false) + { + var class_arr = new SimpleClass[] + { + new SimpleClass { X = 5, Y = -2, Id = "class_arr#Id#0", Color = RGB.Green }, + null, + new SimpleClass { X = 123, Y = 0, Id = "class_arr#Id#2", Color = RGB.Blue }, + }; + + var class_arr_empty = new SimpleClass[0]; + SimpleClass[] class_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"class_arr: {class_arr.Length}, {class_arr_empty.Length}, {class_arr_null?.Length}"); + } + + public static void GenericTypeLocals(bool call_other = false) + { + var gclass_arr = new GenericClass[] + { + null, + new GenericClass { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 }, + new GenericClass { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 }, + }; + + var gclass_arr_empty = new GenericClass[0]; + GenericClass[] gclass_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"gclass_arr: {gclass_arr.Length}, {gclass_arr_empty.Length}, {gclass_arr_null?.Length}"); + } + + public static void GenericValueTypeLocals(bool call_other = false) + { + var gvclass_arr = new SimpleGenericStruct[] + { + new SimpleGenericStruct { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } }, + new SimpleGenericStruct { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } } + }; + + var gvclass_arr_empty = new SimpleGenericStruct[0]; + SimpleGenericStruct[] gvclass_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); + } + + static void OtherMethod() + { + YetAnotherMethod(); + Console.WriteLine($"Just a placeholder for breakpoints"); + } + + static void YetAnotherMethod() + { + Console.WriteLine($"Just a placeholder for breakpoints"); + } + + public static void ObjectArrayMembers() + { + var c = new Container + { + id = "c#id", + ClassArrayProperty = new SimpleClass[] + { + new SimpleClass { X = 5, Y = -2, Id = "ClassArrayProperty#Id#0", Color = RGB.Green }, + new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayProperty#Id#1", Color = RGB.Green }, + null + }, + ClassArrayField = new SimpleClass[] + { + null, + new SimpleClass { X = 5, Y = -2, Id = "ClassArrayField#Id#1", Color = RGB.Blue }, + new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayField#Id#2", Color = RGB.Green }, + }, + PointsProperty = new Point[] + { + new Point { X = 5, Y = -2, Id = "PointsProperty#Id#0", Color = RGB.Green }, + new Point { X = 123, Y = 0, Id = "PointsProperty#Id#1", Color = RGB.Blue }, + }, + PointsField = new Point[] + { + new Point { X = 5, Y = -2, Id = "PointsField#Id#0", Color = RGB.Green }, + new Point { X = 123, Y = 0, Id = "PointsField#Id#1", Color = RGB.Blue }, + } + }; + + Console.WriteLine($"Back from PlaceholderMethod, {c.ClassArrayProperty?.Length}"); + c.PlaceholderMethod(); + Console.WriteLine($"Back from PlaceholderMethod, {c.id}"); + } + + public static async Task ValueTypeLocalsAsync(bool call_other = false) + { + var gvclass_arr = new SimpleGenericStruct[] + { + new SimpleGenericStruct { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } }, + new SimpleGenericStruct { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } } + }; + + var gvclass_arr_empty = new SimpleGenericStruct[0]; + SimpleGenericStruct[] gvclass_arr_null = null; + Console.WriteLine($"ValueTypeLocalsAsync: call_other: {call_other}"); + SimpleGenericStruct gvclass; + Point[] points = null; + + if (call_other) + { + (gvclass, points) = await new ArrayTestsClass().InstanceMethodValueTypeLocalsAsync>(gvclass_arr[0]); + Console.WriteLine($"* gvclass: {gvclass}, points: {points.Length}"); + } + + Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); + return true; + } + + public async Task<(T, Point[])> InstanceMethodValueTypeLocalsAsync(T t1) + { + var point_arr = new Point[] + { + new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Red }, + new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue } + }; + var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green }; + + Console.WriteLine($"point_arr: {point_arr.Length}, T: {t1}, point: {point}"); + return (t1, new Point[] { point_arr[0], point_arr[1], point }); + } + + // A workaround for method invocations on structs not working right now + public static async Task EntryPointForStructMethod(bool call_other = false) + { + await Point.AsyncMethod(call_other); + } + + public static void GenericValueTypeLocals2(bool call_other = false) + { + var gvclass_arr = new SimpleGenericStruct[] + { + new SimpleGenericStruct + { + Id = "gvclass_arr#0#Id", + Color = RGB.Red, + Value = new Point[] + { + new Point { X = 100, Y = 200, Id = "gvclass_arr#0#0#Value#Id", Color = RGB.Red }, + new Point { X = 100, Y = 200, Id = "gvclass_arr#0#1#Value#Id", Color = RGB.Green } + } + }, + + new SimpleGenericStruct + { + Id = "gvclass_arr#1#Id", + Color = RGB.Blue, + Value = new Point[] + { + new Point { X = 100, Y = 200, Id = "gvclass_arr#1#0#Value#Id", Color = RGB.Green }, + new Point { X = 100, Y = 200, Id = "gvclass_arr#1#1#Value#Id", Color = RGB.Blue } + } + }, + }; + + var gvclass_arr_empty = new SimpleGenericStruct[0]; + SimpleGenericStruct[] gvclass_arr_null = null; + + if (call_other) + OtherMethod(); + + Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}"); + } + } + + public class Container + { + public string id; + public SimpleClass[] ClassArrayProperty { get; set; } + public SimpleClass[] ClassArrayField; + + public Point[] PointsProperty { get; set; } + public Point[] PointsField; + + public void PlaceholderMethod() + { + Console.WriteLine($"Container.PlaceholderMethod"); + } + } + + public struct Point + { + public int X, Y; + public string Id { get; set; } + public RGB Color { get; set; } + + /* instance too */ + public static async Task AsyncMethod(bool call_other) + { + int local_i = 5; + var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue }; + if (call_other) + await new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.AsyncInstanceMethod(sc); + Console.WriteLine($"AsyncMethod local_i: {local_i}, sc: {sc.Id}"); + } + + public async Task AsyncInstanceMethod(SimpleClass sc_arg) + { + var local_gs = new SimpleGenericStruct { Id = "local_gs#Id", Color = RGB.Green, Value = 4 }; + sc_arg.Id = "sc_arg#Id"; + Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); + } + + public void GenericInstanceMethod(T sc_arg) where T : SimpleClass + { + var local_gs = new SimpleGenericStruct { Id = "local_gs#Id", Color = RGB.Green, Value = 4 }; + sc_arg.Id = "sc_arg#Id"; + Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); + } + } + + public class SimpleClass + { + public int X, Y; + public string Id { get; set; } + public RGB Color { get; set; } + + public Point PointWithCustomGetter { get { return new Point { X = 100, Y = 400, Id = "SimpleClass#Point#gen#Id", Color = RGB.Green }; } } + } + + public class GenericClass + { + public string Id { get; set; } + public RGB Color { get; set; } + public T Value { get; set; } + } + + public struct SimpleGenericStruct + { + public string Id { get; set; } + public RGB Color { get; set; } + public T Value { get; set; } + } + + public class EntryClass + { + public static void run() + { + ArrayTestsClass.PrimitiveTypeLocals(true); + ArrayTestsClass.ValueTypeLocals(true); + ArrayTestsClass.ObjectTypeLocals(true); + + ArrayTestsClass.GenericTypeLocals(true); + ArrayTestsClass.GenericValueTypeLocals(true); + ArrayTestsClass.GenericValueTypeLocals2(true); + + ArrayTestsClass.ObjectArrayMembers(); + + ArrayTestsClass.ValueTypeLocalsAsync(true).Wait(); + + ArrayTestsClass.EntryPointForStructMethod(true).Wait(); + + var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue }; + new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.GenericInstanceMethod(sc); + } + } } diff --git a/sdks/wasm/tests/debugger/debugger-cfo-test.cs b/sdks/wasm/tests/debugger/debugger-cfo-test.cs index b99c093ab63b..66a964a28081 100644 --- a/sdks/wasm/tests/debugger/debugger-cfo-test.cs +++ b/sdks/wasm/tests/debugger/debugger-cfo-test.cs @@ -1,60 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; namespace DebuggerTests { - public class CallFunctionOnTest { - public static void LocalsTest (int len) - { - var big = new int[len]; - for (int i = 0; i < len; i ++) - big [i] = i + 1000; - - var simple_struct = new Math.SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5), gs = new Math.GenericStruct { StringField = $"simple_struct # gs # StringField" } }; - - var ss_arr = new Math.SimpleStruct [len]; - for (int i = 0; i < len; i ++) - ss_arr [i] = new Math.SimpleStruct () { dt = new DateTime (2020+i, 1, 2, 3, 4, 5), gs = new Math.GenericStruct { StringField = $"ss_arr # {i} # gs # StringField" } }; - - var nim = new Math.NestedInMath { SimpleStructProperty = new Math.SimpleStruct () { dt = new DateTime (2010, 6, 7, 8, 9, 10) } }; - Action> action = Math.DelegateTargetWithVoidReturn; - Console.WriteLine("foo"); - } - - public static void PropertyGettersTest () - { - var ptd = new ClassWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9) }; - var swp = new StructWithProperties (); - System.Console.WriteLine("break here"); - } - - public static async System.Threading.Tasks.Task PropertyGettersTestAsync () - { - var ptd = new ClassWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9) }; - var swp = new StructWithProperties (); - System.Console.WriteLine("break here"); - await System.Threading.Tasks.Task.CompletedTask; - } - } - - class ClassWithProperties - { - public int Int { get { return 5; } } - public string String { get { return "foobar"; } } - public DateTime DT { get { return new DateTime (3, 4, 5, 6, 7, 8); } } - - public int[] IntArray { get { return new int[] { 10, 20 }; } } - public DateTime[] DTArray { get { return new DateTime[] { new DateTime (6, 7, 8, 9, 10, 11), new DateTime (1, 2, 3, 4, 5, 6) }; }} - public DateTime DTAutoProperty { get; set; } - public string StringField; - } - - struct StructWithProperties - { - public int Int { get { return 5; } } - public string String { get { return "foobar"; } } - public DateTime DT { get { return new DateTime (3, 4, 5, 6, 7, 8); } } - - public int[] IntArray { get { return new int[] { 10, 20 }; } } - public DateTime[] DTArray { get { return new DateTime[] { new DateTime (6, 7, 8, 9, 10, 11), new DateTime (1, 2, 3, 4, 5, 6) }; }} - } + public class CallFunctionOnTest + { + public static void LocalsTest(int len) + { + var big = new int[len]; + for (int i = 0; i < len; i++) + big[i] = i + 1000; + + var simple_struct = new Math.SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5), gs = new Math.GenericStruct { StringField = $"simple_struct # gs # StringField" } }; + + var ss_arr = new Math.SimpleStruct[len]; + for (int i = 0; i < len; i++) + ss_arr[i] = new Math.SimpleStruct() { dt = new DateTime(2020 + i, 1, 2, 3, 4, 5), gs = new Math.GenericStruct { StringField = $"ss_arr # {i} # gs # StringField" } }; + + var nim = new Math.NestedInMath { SimpleStructProperty = new Math.SimpleStruct() { dt = new DateTime(2010, 6, 7, 8, 9, 10) } }; + Action> action = Math.DelegateTargetWithVoidReturn; + Console.WriteLine("foo"); + } + + public static void PropertyGettersTest() + { + var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF }; + var swp = new StructWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF }; + System.Console.WriteLine("break here"); + } + + public static async System.Threading.Tasks.Task PropertyGettersTestAsync() + { + var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF }; + var swp = new StructWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF }; + System.Console.WriteLine("break here"); + await System.Threading.Tasks.Task.CompletedTask; + } + + public static void MethodForNegativeTests(string value = null) + { + var ptd = new ClassWithProperties { StringField = value }; + var swp = new StructWithProperties { StringField = value }; + Console.WriteLine("break here"); + } + } + + class ClassWithProperties + { + public uint V; + public uint Int { get { return V + (uint)DT.Month; } } + public string String { get { return $"String property, V: 0x{V:X}"; } } + public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } } + + public int[] IntArray { get { return new int[] { 10, 20 }; } } + public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } } + public DateTime DTAutoProperty { get; set; } + public string StringField; + } + + struct StructWithProperties + { + public uint V; + public uint Int { get { return V + (uint)DT.Month; } } + public string String { get { return $"String property, V: 0x{V:X}"; } } + public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } } + + public int[] IntArray { get { return new int[] { 10, 20 }; } } + public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } } + public DateTime DTAutoProperty { get; set; } + public string StringField; + } } diff --git a/sdks/wasm/tests/debugger/debugger-datetime-test.cs b/sdks/wasm/tests/debugger/debugger-datetime-test.cs index 55a56761966e..62890a7c0ff8 100644 --- a/sdks/wasm/tests/debugger/debugger-datetime-test.cs +++ b/sdks/wasm/tests/debugger/debugger-datetime-test.cs @@ -1,24 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Globalization; -namespace DebuggerTests { - public class DateTimeTest { - public static string LocaleTest (string locale) - { - CultureInfo.CurrentCulture = new CultureInfo (locale, false); - Console.WriteLine("CurrentCulture is {0}", CultureInfo.CurrentCulture.Name); +namespace DebuggerTests +{ + public class DateTimeTest + { + public static string LocaleTest(string locale) + { + CultureInfo.CurrentCulture = new CultureInfo(locale, false); + Console.WriteLine("CurrentCulture is {0}", CultureInfo.CurrentCulture.Name); - DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; - var fdtp = dtfi.FullDateTimePattern; - var ldp = dtfi.LongDatePattern; - var ltp = dtfi.LongTimePattern; - var sdp = dtfi.ShortDatePattern; - var stp = dtfi.ShortTimePattern; + DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; + var fdtp = dtfi.FullDateTimePattern; + var ldp = dtfi.LongDatePattern; + var ltp = dtfi.LongTimePattern; + var sdp = dtfi.ShortDatePattern; + var stp = dtfi.ShortTimePattern; - DateTime dt = new DateTime (2020, 1, 2, 3, 4, 5); - string dt_str = dt.ToString(); - Console.WriteLine("Current time is {0}", dt_str); + DateTime dt = new DateTime(2020, 1, 2, 3, 4, 5); + string dt_str = dt.ToString(); + Console.WriteLine("Current time is {0}", dt_str); - return dt_str; - } - } -} \ No newline at end of file + return dt_str; + } + } +} diff --git a/sdks/wasm/tests/debugger/debugger-driver.html b/sdks/wasm/tests/debugger/debugger-driver.html index 160a4e53b1e3..049b6b65c600 100644 --- a/sdks/wasm/tests/debugger/debugger-driver.html +++ b/sdks/wasm/tests/debugger/debugger-driver.html @@ -75,7 +75,7 @@ } - + Stuff goes here diff --git a/sdks/wasm/tests/debugger/debugger-evaluate-test.cs b/sdks/wasm/tests/debugger/debugger-evaluate-test.cs index 869708a64da3..498c02832e33 100644 --- a/sdks/wasm/tests/debugger/debugger-evaluate-test.cs +++ b/sdks/wasm/tests/debugger/debugger-evaluate-test.cs @@ -1,103 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Threading.Tasks; namespace DebuggerTests { - public class EvaluateTestsClass - { - public class TestEvaluate { + public class EvaluateTestsClass + { + public class TestEvaluate + { public int a; public int b; public int c; - public DateTime dt = new DateTime (2000, 5, 4, 3, 2, 1); - public void run(int g, int h, string valString) { + public DateTime dt = new DateTime(2000, 5, 4, 3, 2, 1); + public TestEvaluate NullIfAIsNotZero => a != 0 ? null : this; + public void run(int g, int h, string a, string valString, int this_a) + { int d = g + 1; int e = g + 2; int f = g + 3; int i = d + e + f; - var local_dt = new DateTime (2010, 9, 8, 7, 6, 5); - a = 1; + var local_dt = new DateTime(2010, 9, 8, 7, 6, 5); + this.a = 1; b = 2; c = 3; - a = a + 1; + this.a = this.a + 1; b = b + 1; c = c + 1; } - } + } + + public static void EvaluateLocals() + { + TestEvaluate f = new TestEvaluate(); + f.run(100, 200, "9000", "test", 45); + + var f_s = new EvaluateTestsStructWithProperties(); + f_s.InstanceMethod(100, 200, "test", f_s); + f_s.GenericInstanceMethod(100, 200, "test", f_s); + + var f_g_s = new EvaluateTestsGenericStruct(); + f_g_s.EvaluateTestsGenericStructInstanceMethod(100, 200, "test"); + } + + } + + public struct EvaluateTestsGenericStruct + { + public int a; + public int b; + public int c; + DateTime dateTime; + public void EvaluateTestsGenericStructInstanceMethod(int g, int h, string valString) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + T t = default(T); + a = a + 1; + b = b + 2; + c = c + 3; + } + } + + public class EvaluateTestsClassWithProperties + { + public int a; + public int b; + public int c { get; set; } + + public DateTime dateTime; + public DateTime DTProp => dateTime.AddMinutes(10); + public int IntProp => a + 5; + public string SetOnlyProp { set { a = value.Length; } } + public EvaluateTestsClassWithProperties NullIfAIsNotZero => a != 1908712 ? null : new EvaluateTestsClassWithProperties(0); + public EvaluateTestsClassWithProperties NewInstance => new EvaluateTestsClassWithProperties(3); + + public EvaluateTestsClassWithProperties(int bias) + { + a = 4; + b = 0; + c = 0; + dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); + } + + public static async Task run() + { + var obj = new EvaluateTestsClassWithProperties(0); + var obj2 = new EvaluateTestsClassWithProperties(0); + obj.InstanceMethod(400, 123, "just a test", obj2); + new EvaluateTestsClassWithProperties(0).GenericInstanceMethod(400, 123, "just a test", obj2); + new EvaluateTestsClassWithProperties(0).EvaluateShadow(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance); + + await new EvaluateTestsClassWithProperties(0).InstanceMethodAsync(400, 123, "just a test", obj2); + await new EvaluateTestsClassWithProperties(0).GenericInstanceMethodAsync(400, 123, "just a test", obj2); + await new EvaluateTestsClassWithProperties(0).EvaluateShadowAsync(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance); + } + + public void EvaluateShadow(DateTime dateTime, EvaluateTestsClassWithProperties me) + { + string a = "hello"; + Console.WriteLine($"Evaluate - break here"); + SomeMethod(dateTime, me); + } + + public async Task EvaluateShadowAsync(DateTime dateTime, EvaluateTestsClassWithProperties me) + { + string a = "hello"; + Console.WriteLine($"EvaluateShadowAsync - break here"); + await Task.CompletedTask; + } - public static void EvaluateLocals () - { - TestEvaluate f = new TestEvaluate(); - f.run(100, 200, "test"); + public void SomeMethod(DateTime me, EvaluateTestsClassWithProperties dateTime) + { + Console.WriteLine($"break here"); - var f_s = new EvaluateTestsStruct (); - f_s.EvaluateTestsStructInstanceMethod (100, 200, "test"); - f_s.GenericInstanceMethodOnStruct (100, 200, "test"); + var DTProp = "hello"; + Console.WriteLine($"dtProp: {DTProp}"); + } - var f_g_s = new EvaluateTestsGenericStruct (); - f_g_s.EvaluateTestsGenericStructInstanceMethod (100, 200, "test"); - Console.WriteLine ($"a: {f.a}, b: {f.b}, c: {f.c}"); - } + public async Task InstanceMethodAsync(int g, int h, string valString, EvaluateTestsClassWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + await Task.CompletedTask; + } + public void InstanceMethod(int g, int h, string valString, EvaluateTestsClassWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + } + + public void GenericInstanceMethod(int g, int h, string valString, EvaluateTestsClassWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + T t = default(T); + } + + public async Task GenericInstanceMethodAsync(int g, int h, string valString, EvaluateTestsClassWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + T t = default(T); + return await Task.FromResult(default(T)); + } } - public struct EvaluateTestsStruct - { - public int a; - public int b; - public int c; - DateTime dateTime; - public void EvaluateTestsStructInstanceMethod (int g, int h, string valString) - { - int d = g + 1; - int e = g + 2; - int f = g + 3; - int i = d + e + f; - a = 1; - b = 2; - c = 3; - dateTime = new DateTime (2020, 1, 2, 3, 4, 5); - a = a + 1; - b = b + 1; - c = c + 1; - } - - public void GenericInstanceMethodOnStruct (int g, int h, string valString) - { - int d = g + 1; - int e = g + 2; - int f = g + 3; - int i = d + e + f; - a = 1; - b = 2; - c = 3; - dateTime = new DateTime (2020, 1, 2, 3, 4, 5); - T t = default(T); - a = a + 1; - b = b + 1; - c = c + 1; - } - } - - public struct EvaluateTestsGenericStruct - { - public int a; - public int b; - public int c; - DateTime dateTime; - public void EvaluateTestsGenericStructInstanceMethod (int g, int h, string valString) - { - int d = g + 1; - int e = g + 2; - int f = g + 3; - int i = d + e + f; - a = 1; - b = 2; - c = 3; - dateTime = new DateTime (2020, 1, 2, 3, 4, 5); - T t = default(T); - a = a + 1; - b = b + 2; - c = c + 3; - } - } + public struct EvaluateTestsStructWithProperties + { + public int a; + public int b; + public int c { get; set; } + + public DateTime dateTime; + public DateTime DTProp => dateTime.AddMinutes(10); + public int IntProp => a + 5; + public string SetOnlyProp { set { a = value.Length; } } + public EvaluateTestsClassWithProperties NullIfAIsNotZero => a != 1908712 ? null : new EvaluateTestsClassWithProperties(0); + public EvaluateTestsStructWithProperties NewInstance => new EvaluateTestsStructWithProperties(3); + + public EvaluateTestsStructWithProperties(int bias) + { + a = 4; + b = 0; + c = 0; + dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); + } + + public static async Task run() + { + var obj = new EvaluateTestsStructWithProperties(0); + var obj2 = new EvaluateTestsStructWithProperties(0); + obj.InstanceMethod(400, 123, "just a test", obj2); + new EvaluateTestsStructWithProperties(0).GenericInstanceMethod(400, 123, "just a test", obj2); + new EvaluateTestsStructWithProperties(0).EvaluateShadow(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance); + + await new EvaluateTestsStructWithProperties(0).InstanceMethodAsync(400, 123, "just a test", obj2); + await new EvaluateTestsStructWithProperties(0).GenericInstanceMethodAsync(400, 123, "just a test", obj2); + await new EvaluateTestsStructWithProperties(0).EvaluateShadowAsync(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance); + } + + public void EvaluateShadow(DateTime dateTime, EvaluateTestsStructWithProperties me) + { + string a = "hello"; + Console.WriteLine($"Evaluate - break here"); + SomeMethod(dateTime, me); + } + + public async Task EvaluateShadowAsync(DateTime dateTime, EvaluateTestsStructWithProperties me) + { + string a = "hello"; + Console.WriteLine($"EvaluateShadowAsync - break here"); + await Task.CompletedTask; + } + + public void SomeMethod(DateTime me, EvaluateTestsStructWithProperties dateTime) + { + Console.WriteLine($"break here"); + + var DTProp = "hello"; + Console.WriteLine($"dtProp: {DTProp}"); + } + + public async Task InstanceMethodAsync(int g, int h, string valString, EvaluateTestsStructWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + await Task.CompletedTask; + } + + public void InstanceMethod(int g, int h, string valString, EvaluateTestsStructWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + } + + public void GenericInstanceMethod(int g, int h, string valString, EvaluateTestsStructWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + T t = default(T); + } + + public async Task GenericInstanceMethodAsync(int g, int h, string valString, EvaluateTestsStructWithProperties me) + { + int d = g + 1; + int e = g + 2; + int f = g + 3; + var local_dt = new DateTime(2025, 3, 5, 7, 9, 11); + a = 1; + b = 2; + c = 3; + dateTime = new DateTime(2020, 1, 2, 3, 4, 5); + a = a + 1; + b = b + 1; + c = c + 1; + T t = default(T); + return await Task.FromResult(default(T)); + } + } } diff --git a/sdks/wasm/tests/debugger/debugger-exception-test.cs b/sdks/wasm/tests/debugger/debugger-exception-test.cs new file mode 100644 index 000000000000..86904ca27ea8 --- /dev/null +++ b/sdks/wasm/tests/debugger/debugger-exception-test.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +namespace DebuggerTests +{ + public class ExceptionTestsClass + { + public class TestCaughtException + { + public void run() + { + try + { + throw new CustomException("not implemented caught"); + } + catch + { + Console.WriteLine("caught exception"); + } + } + } + + public class TestUncaughtException + { + public void run() + { + throw new CustomException("not implemented uncaught"); + } + } + + public static void TestExceptions() + { + TestCaughtException f = new TestCaughtException(); + f.run(); + + TestUncaughtException g = new TestUncaughtException(); + g.run(); + } + + } + + public class CustomException : Exception + { + // Using this name to match with what js has. + // helps with the tests + public string message; + public CustomException(string message) + : base(message) + { + this.message = message; + } + } +} diff --git a/sdks/wasm/tests/debugger/debugger-pointers-test.cs b/sdks/wasm/tests/debugger/debugger-pointers-test.cs index 6cca499566ea..a82973e5ac37 100644 --- a/sdks/wasm/tests/debugger/debugger-pointers-test.cs +++ b/sdks/wasm/tests/debugger/debugger-pointers-test.cs @@ -1,109 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Threading.Tasks; namespace DebuggerTests { - public class PointerTests - { - - public static unsafe void LocalPointers () - { - int ivalue0 = 5; - int ivalue1 = 10; - - int* ip = &ivalue0; - int* ip_null = null; - int** ipp = &ip; - int** ipp_null = &ip_null; - int*[] ipa = new int*[] { &ivalue0, &ivalue1, null }; - int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null }; - char cvalue0 = 'q'; - char* cp = &cvalue0; - - DateTime dt = new DateTime(5, 6, 7, 8, 9, 10); - void* vp = &dt; - void* vp_null = null; - void** vpp = &vp; - void** vpp_null = &vp_null; - - DateTime* dtp = &dt; - DateTime* dtp_null = null; - DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null }; - DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; - Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}"); - - var gs = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; - var gs_null = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null }; - var gsp = &gs; - var gsp_null = &gs_null; - var gspa = new GenericStructWithUnmanagedT*[] { null, gsp, gsp_null }; - - var cwp = new GenericClassWithPointers { Ptr = dtp }; - var cwp_null = new GenericClassWithPointers(); - Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}"); - - PointersAsArgsTest (ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa); - } - - static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa, - DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa) - { - Console.WriteLine($"break here!"); - if (ip == null) - Console.WriteLine($"ip is null"); - Console.WriteLine($"done!"); - } - - public static unsafe async Task LocalPointersAsync () - { - int ivalue0 = 5; - int ivalue1 = 10; - - int* ip = &ivalue0; - int* ip_null = null; - int** ipp = &ip; - int** ipp_null = &ip_null; - int*[] ipa = new int*[] { &ivalue0, &ivalue1, null }; - int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null }; - char cvalue0 = 'q'; - char* cp = &cvalue0; - - DateTime dt = new DateTime(5, 6, 7, 8, 9, 10); - void* vp = &dt; - void* vp_null = null; - void** vpp = &vp; - void** vpp_null = &vp_null; - - DateTime* dtp = &dt; - DateTime* dtp_null = null; - DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null }; - DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; - Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}"); - - var gs = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; - var gs_null = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null }; - var gsp = &gs; - var gsp_null = &gs_null; - var gspa = new GenericStructWithUnmanagedT*[] { null, gsp, gsp_null }; - - var cwp = new GenericClassWithPointers { Ptr = dtp }; - var cwp_null = new GenericClassWithPointers(); - Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}"); - } - - // async methods cannot have unsafe params, so no test for that - } - - public unsafe struct GenericStructWithUnmanagedT where T : unmanaged - { - public T Value; - public int IntField; - - public DateTime** DTPP; - } - - public unsafe class GenericClassWithPointers where T : unmanaged - { - public unsafe T* Ptr; - } -} \ No newline at end of file + public class PointerTests + { + + public static unsafe void LocalPointers() + { + int ivalue0 = 5; + int ivalue1 = 10; + + int* ip = &ivalue0; + int* ip_null = null; + int** ipp = &ip; + int** ipp_null = &ip_null; + int*[] ipa = new int*[] { &ivalue0, &ivalue1, null }; + int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null }; + char cvalue0 = 'q'; + char* cp = &cvalue0; + + DateTime dt = new DateTime(5, 6, 7, 8, 9, 10); + void* vp = &dt; + void* vp_null = null; + void** vpp = &vp; + void** vpp_null = &vp_null; + + DateTime* dtp = &dt; + DateTime* dtp_null = null; + DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null }; + DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; + Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}"); + + var gs = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; + var gs_null = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null }; + var gsp = &gs; + var gsp_null = &gs_null; + var gspa = new GenericStructWithUnmanagedT*[] { null, gsp, gsp_null }; + + var cwp = new GenericClassWithPointers { Ptr = dtp }; + var cwp_null = new GenericClassWithPointers(); + Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}"); + + PointersAsArgsTest(ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa); + } + + static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa, + DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa) + { + Console.WriteLine($"break here!"); + if (ip == null) + Console.WriteLine($"ip is null"); + Console.WriteLine($"done!"); + } + + public static unsafe async Task LocalPointersAsync() + { + int ivalue0 = 5; + int ivalue1 = 10; + + int* ip = &ivalue0; + int* ip_null = null; + int** ipp = &ip; + int** ipp_null = &ip_null; + int*[] ipa = new int*[] { &ivalue0, &ivalue1, null }; + int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null }; + char cvalue0 = 'q'; + char* cp = &cvalue0; + + DateTime dt = new DateTime(5, 6, 7, 8, 9, 10); + void* vp = &dt; + void* vp_null = null; + void** vpp = &vp; + void** vpp_null = &vp_null; + + DateTime* dtp = &dt; + DateTime* dtp_null = null; + DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null }; + DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null }; + Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}"); + + var gs = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; + var gs_null = new GenericStructWithUnmanagedT { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null }; + var gsp = &gs; + var gsp_null = &gs_null; + var gspa = new GenericStructWithUnmanagedT*[] { null, gsp, gsp_null }; + + var cwp = new GenericClassWithPointers { Ptr = dtp }; + var cwp_null = new GenericClassWithPointers(); + Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}"); + } + + // async methods cannot have unsafe params, so no test for that + } + + public unsafe struct GenericStructWithUnmanagedT where T : unmanaged + { + public T Value; + public int IntField; + + public DateTime** DTPP; + } + + public unsafe class GenericClassWithPointers where T : unmanaged + { + public unsafe T* Ptr; + } +} diff --git a/sdks/wasm/tests/debugger/debugger-test.cs b/sdks/wasm/tests/debugger/debugger-test.cs index 84e3bcfe0a74..9b53cea250ce 100644 --- a/sdks/wasm/tests/debugger/debugger-test.cs +++ b/sdks/wasm/tests/debugger/debugger-test.cs @@ -1,309 +1,325 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; -public partial class Math { //Only append content to this class as the test suite depends on line info - public static int IntAdd (int a, int b) { - int c = a + b; - int d = c + b; - int e = d + a; - int f = 0; - return e; - } - - public static int UseComplex (int a, int b) { - var complex = new Simple.Complex (10, "xx"); - int c = a + b; - int d = c + b; - int e = d + a; - int f = 0; - e += complex.DoStuff (); - return e; - } - - delegate bool IsMathNull (Math m); - - public static int DelegatesTest () { - Func fn_func = (Math m) => m == null; - Func fn_func_null = null; - Func[] fn_func_arr = new Func[] { (Math m) => m == null }; - - Math.IsMathNull fn_del = Math.IsMathNullDelegateTarget; - var fn_del_arr = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget }; - var m_obj = new Math (); - Math.IsMathNull fn_del_null = null; - bool res = fn_func (m_obj) && fn_del (m_obj) && fn_del_arr[0] (m_obj) && fn_del_null == null && fn_func_null == null && fn_func_arr[0] != null; - - // Unused locals - - Func fn_func_unused = (Math m) => m == null; - Func fn_func_null_unused = null; - Func[] fn_func_arr_unused = new Func[] { (Math m) => m == null }; - - Math.IsMathNull fn_del_unused = Math.IsMathNullDelegateTarget; - Math.IsMathNull fn_del_null_unused = null; - var fn_del_arr_unused = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget }; - OuterMethod (); - Console.WriteLine ("Just a test message, ignore"); - return res ? 0 : 1; - } - - public static int GenericTypesTest () { - var list = new System.Collections.Generic.Dictionary (); - System.Collections.Generic.Dictionary list_null = null; - - var list_arr = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary () }; - System.Collections.Generic.Dictionary[] list_arr_null = null; - - Console.WriteLine ($"list_arr.Length: {list_arr.Length}, list.Count: {list.Count}"); - - // Unused locals - - var list_unused = new System.Collections.Generic.Dictionary (); - System.Collections.Generic.Dictionary list_null_unused = null; - - var list_arr_unused = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary () }; - System.Collections.Generic.Dictionary[] list_arr_null_unused = null; - - OuterMethod (); - Console.WriteLine ("Just a test message, ignore"); - return 0; - } - - static bool IsMathNullDelegateTarget (Math m) => m == null; - - public static void OuterMethod () - { - Console.WriteLine ($"OuterMethod called"); - var nim = new Math.NestedInMath (); - var i = 5; - var text = "Hello"; - var new_i = nim.InnerMethod (i); - Console.WriteLine ($"i: {i}"); - Console.WriteLine ($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}"); - int k = 19; - new_i = InnerMethod2 ("test string", new_i, out k); - Console.WriteLine ($"-- InnerMethod2 returned: {new_i}, and k: {k}"); - } - - static int InnerMethod2 (string s, int i, out int k) - { - k = i + 10; - Console.WriteLine ($"s: {s}, i: {i}, k: {k}"); - return i - 2; - } - - public class NestedInMath - { - public int InnerMethod (int i) - { - SimpleStructProperty = new SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5) }; - int j = i + 10; - string foo_str = "foo"; - Console.WriteLine ($"i: {i} and j: {j}, foo_str: {foo_str} "); - j += 9; - Console.WriteLine ($"i: {i} and j: {j}"); - return j; - } - - Math m = new Math (); - public async System.Threading.Tasks.Task AsyncMethod0 (string s, int i) - { - string local0 = "value0"; - await System.Threading.Tasks.Task.Delay (1); - Console.WriteLine ($"* time for the second await, local0: {local0}"); - await AsyncMethodNoReturn (); - return true; - } - - public async System.Threading.Tasks.Task AsyncMethodNoReturn () - { - var ss = new SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5) }; - var ss_arr = new SimpleStruct [] {}; - //ss.gs.StringField = "field in GenericStruct"; - - //Console.WriteLine ($"Using the struct: {ss.dt}, {ss.gs.StringField}, ss_arr: {ss_arr.Length}"); - string str = "AsyncMethodNoReturn's local"; - //Console.WriteLine ($"* field m: {m}"); - await System.Threading.Tasks.Task.Delay (1); - Console.WriteLine ($"str: {str}"); - } - - public static async System.Threading.Tasks.Task AsyncTest (string s, int i) - { - var li = 10 + i; - var ls = s + "test"; - return await new NestedInMath().AsyncMethod0 (s, i); - } - - public SimpleStruct SimpleStructProperty { get; set; } - } - - public static void PrimitiveTypesTest () - { - char c0 = '€'; - char c1 = 'A'; - // TODO: other types! - // just trying to ensure vars don't get optimized out - if (c0 < 32 || c1 > 32) - Console.WriteLine ($"{c0}, {c1}"); - } - - public static int DelegatesSignatureTest () - { - Func>, GenericStruct> fn_func = (m, gs) => new GenericStruct(); - Func>, GenericStruct> fn_func_del = GenericStruct.DelegateTargetForSignatureTest; - Func>, GenericStruct> fn_func_null = null; - Func fn_func_only_ret = () => { Console.WriteLine ($"hello"); return true; }; - var fn_func_arr = new Func>, GenericStruct>[] { (m, gs) => new GenericStruct() }; - - Math.DelegateForSignatureTest fn_del = GenericStruct.DelegateTargetForSignatureTest; - Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct { StringField = "fn_del_l#lambda" }; - var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct { StringField = "fn_del_arr#1#lambda" } }; - var m_obj = new Math (); - Math.DelegateForSignatureTest fn_del_null = null; - var gs_gs = new GenericStruct> - { - List = new System.Collections.Generic.List> { - new GenericStruct { StringField = "gs#List#0#StringField" }, - new GenericStruct { StringField = "gs#List#1#StringField" } - } - }; - - Math.DelegateWithVoidReturn fn_void_del = Math.DelegateTargetWithVoidReturn; - var fn_void_del_arr = new Math.DelegateWithVoidReturn[] { Math.DelegateTargetWithVoidReturn }; - Math.DelegateWithVoidReturn fn_void_del_null = null; - - var rets = new GenericStruct[] { - fn_func (m_obj, gs_gs), - fn_func_del (m_obj, gs_gs), - fn_del (m_obj, gs_gs), - fn_del_l (m_obj, gs_gs), - fn_del_arr[0] (m_obj, gs_gs), - fn_func_arr[0] (m_obj, gs_gs) - }; - - var gs = new GenericStruct(); - fn_void_del (gs); - fn_void_del_arr[0](gs); - fn_func_only_ret (); - foreach (var ret in rets) Console.WriteLine ($"ret: {ret}"); - OuterMethod (); - Console.WriteLine ($"- {gs_gs.List[0].StringField}"); - return 0; - } - - public static int ActionTSignatureTest () - { - Action> fn_action = (_) => { }; - Action> fn_action_del = Math.DelegateTargetWithVoidReturn; - Action fn_action_bare = () => {}; - Action> fn_action_null = null; - var fn_action_arr = new Action>[] { - (gs) => new GenericStruct(), - Math.DelegateTargetWithVoidReturn, - null - }; - - var gs = new GenericStruct(); - fn_action (gs); - fn_action_del (gs); - fn_action_arr[0](gs); - fn_action_bare (); - OuterMethod (); - return 0; - } - - public static int NestedDelegatesTest () - { - Func, bool> fn_func = (_) => { return true; }; - Func, bool> fn_func_null = null; - var fn_func_arr = new Func, bool>[] { (gs) => { return true; } }; - - var fn_del_arr = new Func, bool>[] { DelegateTargetForNestedFunc> }; - var m_obj = new Math (); - Func, bool> fn_del_null = null; - Func fs = (i) => i == 0; - fn_func (fs); - fn_del_arr[0](fs); - fn_func_arr[0](fs); - OuterMethod (); - return 0; - } - - public static void DelegatesAsMethodArgsTest () - { - var _dst_arr = new DelegateForSignatureTest[] { - GenericStruct.DelegateTargetForSignatureTest, - (m, gs) => new GenericStruct () - }; - Func _fn_func = (cs) => cs.Length == 0; - Action[]> _fn_action = (gss) => { }; - - new Math ().MethodWithDelegateArgs (_dst_arr, _fn_func, _fn_action); - } - - void MethodWithDelegateArgs (Math.DelegateForSignatureTest[] dst_arr, Func fn_func, - Action[]> fn_action) - { - Console.WriteLine ($"Placeholder for breakpoint"); - OuterMethod (); - } - - public static async System.Threading.Tasks.Task MethodWithDelegatesAsyncTest () - { - await new Math ().MethodWithDelegatesAsync (); - } - - async System.Threading.Tasks.Task MethodWithDelegatesAsync () - { - var _dst_arr = new DelegateForSignatureTest[] { - GenericStruct.DelegateTargetForSignatureTest, - (m, gs) => new GenericStruct () - }; - Func _fn_func = (cs) => cs.Length == 0; - Action[]> _fn_action = (gss) => { }; - - Console.WriteLine ($"Placeholder for breakpoint"); - await System.Threading.Tasks.Task.CompletedTask; - } - - public delegate void DelegateWithVoidReturn (GenericStruct gs); - public static void DelegateTargetWithVoidReturn (GenericStruct gs) { } - - delegate GenericStruct DelegateForSignatureTest (Math m, GenericStruct> gs); - static bool DelegateTargetForNestedFunc(T arg) => true; - - public struct SimpleStruct - { - public DateTime dt; - public GenericStruct gs; - } - - public struct GenericStruct - { - public System.Collections.Generic.List List; - public string StringField; - - public static GenericStruct DelegateTargetForSignatureTest (Math m, GenericStruct> gs) - => new GenericStruct (); - } +public partial class Math +{ //Only append content to this class as the test suite depends on line info + public static int IntAdd(int a, int b) + { + int c = a + b; + int d = c + b; + int e = d + a; + int f = 0; + return e; + } + + public static int UseComplex(int a, int b) + { + var complex = new Simple.Complex(10, "xx"); + int c = a + b; + int d = c + b; + int e = d + a; + int f = 0; + e += complex.DoStuff(); + return e; + } + + delegate bool IsMathNull(Math m); + + public static int DelegatesTest() + { + Func fn_func = (Math m) => m == null; + Func fn_func_null = null; + Func[] fn_func_arr = new Func[] { + (Math m) => m == null }; + + Math.IsMathNull fn_del = Math.IsMathNullDelegateTarget; + var fn_del_arr = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget }; + var m_obj = new Math(); + Math.IsMathNull fn_del_null = null; + bool res = fn_func(m_obj) && fn_del(m_obj) && fn_del_arr[0](m_obj) && fn_del_null == null && fn_func_null == null && fn_func_arr[0] != null; + + // Unused locals + + Func fn_func_unused = (Math m) => m == null; + Func fn_func_null_unused = null; + Func[] fn_func_arr_unused = new Func[] { (Math m) => m == null }; + + Math.IsMathNull fn_del_unused = Math.IsMathNullDelegateTarget; + Math.IsMathNull fn_del_null_unused = null; + var fn_del_arr_unused = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget }; + OuterMethod(); + Console.WriteLine("Just a test message, ignore"); + return res ? 0 : 1; + } + + public static int GenericTypesTest() + { + var list = new System.Collections.Generic.Dictionary(); + System.Collections.Generic.Dictionary list_null = null; + + var list_arr = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary() }; + System.Collections.Generic.Dictionary[] list_arr_null = null; + + Console.WriteLine($"list_arr.Length: {list_arr.Length}, list.Count: {list.Count}"); + + // Unused locals + + var list_unused = new System.Collections.Generic.Dictionary(); + System.Collections.Generic.Dictionary list_null_unused = null; + + var list_arr_unused = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary() }; + System.Collections.Generic.Dictionary[] list_arr_null_unused = null; + + OuterMethod(); + Console.WriteLine("Just a test message, ignore"); + return 0; + } + + static bool IsMathNullDelegateTarget(Math m) => m == null; + + public static void OuterMethod() + { + Console.WriteLine($"OuterMethod called"); + var nim = new Math.NestedInMath(); + var i = 5; + var text = "Hello"; + var new_i = nim.InnerMethod(i); + Console.WriteLine($"i: {i}"); + Console.WriteLine($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}"); + int k = 19; + new_i = InnerMethod2("test string", new_i, out k); + Console.WriteLine($"-- InnerMethod2 returned: {new_i}, and k: {k}"); + } + + static int InnerMethod2(string s, int i, out int k) + { + k = i + 10; + Console.WriteLine($"s: {s}, i: {i}, k: {k}"); + return i - 2; + } + + public class NestedInMath + { + public int InnerMethod(int i) + { + SimpleStructProperty = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) }; + int j = i + 10; + string foo_str = "foo"; + Console.WriteLine($"i: {i} and j: {j}, foo_str: {foo_str} "); + j += 9; + Console.WriteLine($"i: {i} and j: {j}"); + return j; + } + + Math m = new Math(); + public async System.Threading.Tasks.Task AsyncMethod0(string s, int i) + { + string local0 = "value0"; + await System.Threading.Tasks.Task.Delay(1); + Console.WriteLine($"* time for the second await, local0: {local0}"); + await AsyncMethodNoReturn(); + return true; + } + + public async System.Threading.Tasks.Task AsyncMethodNoReturn() + { + var ss = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) }; + var ss_arr = new SimpleStruct[] { }; + //ss.gs.StringField = "field in GenericStruct"; + + //Console.WriteLine ($"Using the struct: {ss.dt}, {ss.gs.StringField}, ss_arr: {ss_arr.Length}"); + string str = "AsyncMethodNoReturn's local"; + //Console.WriteLine ($"* field m: {m}"); + await System.Threading.Tasks.Task.Delay(1); + Console.WriteLine($"str: {str}"); + } + + public static async System.Threading.Tasks.Task AsyncTest(string s, int i) + { + var li = 10 + i; + var ls = s + "test"; + return await new NestedInMath().AsyncMethod0(s, i); + } + + public SimpleStruct SimpleStructProperty { get; set; } + } + + public static void PrimitiveTypesTest() + { + char c0 = '€'; + char c1 = 'A'; + // TODO: other types! + // just trying to ensure vars don't get optimized out + if (c0 < 32 || c1 > 32) + Console.WriteLine($"{c0}, {c1}"); + } + + public static int DelegatesSignatureTest() + { + Func>, GenericStruct> fn_func = (m, gs) => new GenericStruct(); + Func>, GenericStruct> fn_func_del = GenericStruct.DelegateTargetForSignatureTest; + Func>, GenericStruct> fn_func_null = null; + Func fn_func_only_ret = () => { Console.WriteLine($"hello"); return true; }; + var fn_func_arr = new Func>, GenericStruct>[] { + (m, gs) => new GenericStruct () }; + + Math.DelegateForSignatureTest fn_del = GenericStruct.DelegateTargetForSignatureTest; + Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct { StringField = "fn_del_l#lambda" }; + var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct { StringField = "fn_del_arr#1#lambda" } }; + var m_obj = new Math(); + Math.DelegateForSignatureTest fn_del_null = null; + var gs_gs = new GenericStruct> + { + List = new System.Collections.Generic.List> + { + new GenericStruct { StringField = "gs#List#0#StringField" }, + new GenericStruct { StringField = "gs#List#1#StringField" } + } + }; + + Math.DelegateWithVoidReturn fn_void_del = Math.DelegateTargetWithVoidReturn; + var fn_void_del_arr = new Math.DelegateWithVoidReturn[] { Math.DelegateTargetWithVoidReturn }; + Math.DelegateWithVoidReturn fn_void_del_null = null; + + var rets = new GenericStruct[] + { + fn_func(m_obj, gs_gs), + fn_func_del(m_obj, gs_gs), + fn_del(m_obj, gs_gs), + fn_del_l(m_obj, gs_gs), + fn_del_arr[0](m_obj, gs_gs), + fn_func_arr[0](m_obj, gs_gs) + }; + + var gs = new GenericStruct(); + fn_void_del(gs); + fn_void_del_arr[0](gs); + fn_func_only_ret(); + foreach (var ret in rets) Console.WriteLine($"ret: {ret}"); + OuterMethod(); + Console.WriteLine($"- {gs_gs.List[0].StringField}"); + return 0; + } + + public static int ActionTSignatureTest() + { + Action> fn_action = (_) => { }; + Action> fn_action_del = Math.DelegateTargetWithVoidReturn; + Action fn_action_bare = () => { }; + Action> fn_action_null = null; + var fn_action_arr = new Action>[] + { + (gs) => new GenericStruct(), + Math.DelegateTargetWithVoidReturn, + null + }; + + var gs = new GenericStruct(); + fn_action(gs); + fn_action_del(gs); + fn_action_arr[0](gs); + fn_action_bare(); + OuterMethod(); + return 0; + } + + public static int NestedDelegatesTest() + { + Func, bool> fn_func = (_) => { return true; }; + Func, bool> fn_func_null = null; + var fn_func_arr = new Func, bool>[] { + (gs) => { return true; } }; + + var fn_del_arr = new Func, bool>[] { DelegateTargetForNestedFunc> }; + var m_obj = new Math(); + Func, bool> fn_del_null = null; + Func fs = (i) => i == 0; + fn_func(fs); + fn_del_arr[0](fs); + fn_func_arr[0](fs); + OuterMethod(); + return 0; + } + + public static void DelegatesAsMethodArgsTest() + { + var _dst_arr = new DelegateForSignatureTest[] + { + GenericStruct.DelegateTargetForSignatureTest, + (m, gs) => new GenericStruct() + }; + Func _fn_func = (cs) => cs.Length == 0; + Action[]> _fn_action = (gss) => { }; + + new Math().MethodWithDelegateArgs(_dst_arr, _fn_func, _fn_action); + } + + void MethodWithDelegateArgs(Math.DelegateForSignatureTest[] dst_arr, Func fn_func, + Action[]> fn_action) + { + Console.WriteLine($"Placeholder for breakpoint"); + OuterMethod(); + } + + public static async System.Threading.Tasks.Task MethodWithDelegatesAsyncTest() + { + await new Math().MethodWithDelegatesAsync(); + } + + async System.Threading.Tasks.Task MethodWithDelegatesAsync() + { + var _dst_arr = new DelegateForSignatureTest[] + { + GenericStruct.DelegateTargetForSignatureTest, + (m, gs) => new GenericStruct() + }; + Func _fn_func = (cs) => cs.Length == 0; + Action[]> _fn_action = (gss) => { }; + + Console.WriteLine($"Placeholder for breakpoint"); + await System.Threading.Tasks.Task.CompletedTask; + } + + public delegate void DelegateWithVoidReturn(GenericStruct gs); + public static void DelegateTargetWithVoidReturn(GenericStruct gs) { } + + delegate GenericStruct DelegateForSignatureTest(Math m, GenericStruct> gs); + static bool DelegateTargetForNestedFunc(T arg) => true; + + public struct SimpleStruct + { + public DateTime dt; + public GenericStruct gs; + } + + public struct GenericStruct + { + public System.Collections.Generic.List List; + public string StringField; + + public static GenericStruct DelegateTargetForSignatureTest(Math m, GenericStruct> gs) => new GenericStruct(); + } } public class DebuggerTest { - public static void run_all () { - locals (); - } - - public static int locals () { - int l_int = 1; - char l_char = 'A'; - long l_long = Int64.MaxValue; - ulong l_ulong = UInt64.MaxValue; - locals_inner (); - return 0; - } - - static void locals_inner () { - } + public static void run_all() + { + locals(); + } + + public static int locals() + { + int l_int = 1; + char l_char = 'A'; + long l_long = Int64.MaxValue; + ulong l_ulong = UInt64.MaxValue; + locals_inner(); + return 0; + } + + static void locals_inner() { } } diff --git a/sdks/wasm/tests/debugger/debugger-test2.cs b/sdks/wasm/tests/debugger/debugger-test2.cs index 907cc52e3364..7275bf4a2f10 100644 --- a/sdks/wasm/tests/debugger/debugger-test2.cs +++ b/sdks/wasm/tests/debugger/debugger-test2.cs @@ -1,44 +1,51 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -public class Misc { //Only append content to this class as the test suite depends on line info - public static int CreateObject (int foo, int bar) { - var f = new Fancy () { - Foo = foo, - Bar = bar, - }; +using System; - Console.WriteLine ($"{f.Foo} {f.Bar}"); - return f.Foo + f.Bar; - } +public class Misc +{ //Only append content to this class as the test suite depends on line info + public static int CreateObject(int foo, int bar) + { + var f = new Fancy() + { + Foo = foo, + Bar = bar, + }; + + Console.WriteLine($"{f.Foo} {f.Bar}"); + return f.Foo + f.Bar; + } } -public class Fancy { - public int Foo; - public int Bar { get ; set; } - public static void Types () { - double dPI = System.Math.PI; - float fPI = (float)System.Math.PI; - - int iMax = int.MaxValue; - int iMin = int.MinValue; - uint uiMax = uint.MaxValue; - uint uiMin = uint.MinValue; - - long l = uiMax * (long)2; - long lMax = long.MaxValue; // cannot be represented as double - long lMin = long.MinValue; // cannot be represented as double - - sbyte sbMax = sbyte.MaxValue; - sbyte sbMin = sbyte.MinValue; - byte bMax = byte.MaxValue; - byte bMin = byte.MinValue; - - short sMax = short.MaxValue; - short sMin = short.MinValue; - ushort usMin = ushort.MinValue; - ushort usMax = ushort.MaxValue; - - var d = usMin + usMax; - } +public class Fancy +{ + public int Foo; + public int Bar { get; set; } + public static void Types() + { + double dPI = System.Math.PI; + float fPI = (float)System.Math.PI; + + int iMax = int.MaxValue; + int iMin = int.MinValue; + uint uiMax = uint.MaxValue; + uint uiMin = uint.MinValue; + + long l = uiMax * (long)2; + long lMax = long.MaxValue; // cannot be represented as double + long lMin = long.MinValue; // cannot be represented as double + + sbyte sbMax = sbyte.MaxValue; + sbyte sbMin = sbyte.MinValue; + byte bMax = byte.MaxValue; + byte bMin = byte.MinValue; + + short sMax = short.MaxValue; + short sMin = short.MinValue; + ushort usMin = ushort.MinValue; + ushort usMax = ushort.MaxValue; + + var d = usMin + usMax; + } } - diff --git a/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs b/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs index cbeea53e2670..f557f4e9c3ee 100644 --- a/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs +++ b/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs @@ -1,245 +1,267 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Threading.Tasks; -namespace DebuggerTests { - public class ValueTypesTest { //Only append content to this class as the test suite depends on line info - - public static void MethodWithLocalStructs () - { - var ss_local = new SimpleStruct ("set in MethodWithLocalStructs", 1, DateTimeKind.Utc); - var gs_local = new GenericStruct { StringField = "gs_local#GenericStruct#StringField" }; - - ValueTypesTest vt_local = new ValueTypesTest { - StringField = "string#0", - SimpleStructField = new SimpleStruct ("SimpleStructField#string#0", 5, DateTimeKind.Local), - SimpleStructProperty = new SimpleStruct ("SimpleStructProperty#string#0", 2, DateTimeKind.Utc), DT = new DateTime (2020, 1, 2, 3, 4, 5), RGB = RGB.Blue - }; - Console.WriteLine ($"Using the struct: {ss_local.gs.StringField}, gs: {gs_local.StringField}, {vt_local.StringField}"); - } - - public static void TestStructsAsMethodArgs () - { - var ss_local = new SimpleStruct ("ss_local#SimpleStruct#string#0", 5, DateTimeKind.Local); - var ss_ret = MethodWithStructArgs ("TestStructsAsMethodArgs#label", ss_local, 3); - Console.WriteLine ($"got back ss_local: {ss_local.gs.StringField}, ss_ret: {ss_ret.gs.StringField}"); - } - - static SimpleStruct MethodWithStructArgs (string label, SimpleStruct ss_arg, int x) - { - Console.WriteLine ($"- ss_arg: {ss_arg.str_member}"); - ss_arg.Kind = DateTimeKind.Utc; - ss_arg.str_member = $"ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"; - ss_arg.gs.StringField = $"ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#{x}"; - return ss_arg; - } - - public static async Task MethodWithLocalStructsStaticAsync () - { - var ss_local = new SimpleStruct ("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc); - var gs_local = new GenericStruct { - StringField = "gs_local#GenericStruct#StringField", - List = new System.Collections.Generic.List { 5, 3 }, - Options = Options.Option2 - - }; - - var result = await ss_local.AsyncMethodWithStructArgs (gs_local); - Console.WriteLine ($"Using the struct: {ss_local.gs.StringField}, result: {result}"); - - return result; - } - - public string StringField; - public SimpleStruct SimpleStructProperty { get; set; } - public SimpleStruct SimpleStructField; - - public struct SimpleStruct - { - public string str_member; - public DateTime dt; - public GenericStruct gs; - public DateTimeKind Kind; - - public SimpleStruct (string str, int f, DateTimeKind kind) - { - str_member = $"{str}#SimpleStruct#str_member"; - dt = new DateTime (2020+f, 1+f, 2+f, 3+f, 5+f, 6+f); - gs = new GenericStruct { - StringField = $"{str}#SimpleStruct#gs#StringField", - List = new System.Collections.Generic.List { new DateTime (2010+f, 2+f, 3+f, 10+f, 2+f, 3+f) }, - Options = Options.Option1 - }; - Kind = kind; - } - - public Task AsyncMethodWithStructArgs (GenericStruct gs) - { - Console.WriteLine ($"placeholder line for a breakpoint"); - if (gs.List.Count > 0) - return Task.FromResult (true); - - return Task.FromResult (false); - } - } - - public struct GenericStruct - { - public System.Collections.Generic.List List; - public string StringField; - - public Options Options { get; set; } - } - - public DateTime DT { get; set; } - public RGB RGB; - - public static void MethodWithLocalsForToStringTest (bool call_other) - { - var dt0 = new DateTime (2020, 1, 2, 3, 4, 5); - var dt1 = new DateTime (2010, 5, 4, 3, 2, 1); - var ts = dt0 - dt1; - var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0)); - decimal dec = 123987123; - var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014"); - - var dts = new DateTime [] { - new DateTime (1983, 6, 7, 5, 6, 10), - new DateTime (1999, 10, 15, 1, 2, 3) - }; - - var obj = new ClassForToStringTests { - DT = new DateTime (2004, 10, 15, 1, 2, 3), - DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)), - TS = ts, - Dec = 1239871, - Guid = guid - }; - - var sst = new StructForToStringTests { - DT = new DateTime (2004, 10, 15, 1, 2, 3), - DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)), - TS = ts, - Dec = 1239871, - Guid = guid - }; - Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); - if (call_other) - MethodWithArgumentsForToStringTest (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); - } - - static void MethodWithArgumentsForToStringTest ( - bool call_other, // not really used, just to help with using common code in the tests - DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, - Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) - { - Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); - } - - public static async Task MethodWithLocalsForToStringTestAsync (bool call_other) - { - var dt0 = new DateTime (2020, 1, 2, 3, 4, 5); - var dt1 = new DateTime (2010, 5, 4, 3, 2, 1); - var ts = dt0 - dt1; - var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0)); - decimal dec = 123987123; - var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014"); - - var dts = new DateTime [] { - new DateTime (1983, 6, 7, 5, 6, 10), - new DateTime (1999, 10, 15, 1, 2, 3) - }; - - var obj = new ClassForToStringTests { - DT = new DateTime (2004, 10, 15, 1, 2, 3), - DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)), - TS = ts, - Dec = 1239871, - Guid = guid - }; - - var sst = new StructForToStringTests { - DT = new DateTime (2004, 10, 15, 1, 2, 3), - DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)), - TS = ts, - Dec = 1239871, - Guid = guid - }; - Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); - if (call_other) - await MethodWithArgumentsForToStringTestAsync (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); - } - - static async Task MethodWithArgumentsForToStringTestAsync ( - bool call_other, // not really used, just to help with using common code in the tests - DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, - Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) - { - Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); - } - - public static void MethodUpdatingValueTypeMembers () - { - var obj = new ClassForToStringTests { - DT = new DateTime (1, 2, 3, 4, 5, 6) - }; - var vt = new StructForToStringTests { - DT = new DateTime (4, 5, 6, 7, 8, 9) - }; - Console.WriteLine ($"#1"); - obj.DT = new DateTime (9, 8, 7, 6, 5, 4); - vt.DT = new DateTime (5, 1, 3, 7, 9, 10); - Console.WriteLine ($"#2"); - } - - public static async Task MethodUpdatingValueTypeLocalsAsync () - { - var dt = new DateTime (1, 2, 3, 4, 5, 6); - Console.WriteLine ($"#1"); - dt = new DateTime (9, 8, 7, 6, 5, 4); - Console.WriteLine ($"#2"); - } - - public static void MethodUpdatingVTArrayMembers () - { - var ssta = new [] { - new StructForToStringTests { DT = new DateTime (1, 2, 3, 4, 5, 6) } - }; - Console.WriteLine ($"#1"); - ssta [0].DT = new DateTime (9, 8, 7, 6, 5, 4); - Console.WriteLine ($"#2"); - } - } - - class ClassForToStringTests - { - public DateTime DT; - public DateTimeOffset DTO; - public TimeSpan TS; - public decimal Dec; - public Guid Guid; - } - - struct StructForToStringTests - { - public DateTime DT; - public DateTimeOffset DTO; - public TimeSpan TS; - public decimal Dec; - public Guid Guid; - } - - public enum RGB - { - Red, Green, Blue - } - - [Flags] - public enum Options - { - None = 0, - Option1 = 1, - Option2 = 2, - Option3 = 4, - - All = Option1 | Option3 - } +namespace DebuggerTests +{ + public class ValueTypesTest + { //Only append content to this class as the test suite depends on line info + + public static void MethodWithLocalStructs() + { + var ss_local = new SimpleStruct("set in MethodWithLocalStructs", 1, DateTimeKind.Utc); + var gs_local = new GenericStruct { StringField = $"gs_local#GenericStruct#StringField" }; + + ValueTypesTest vt_local = new ValueTypesTest + { + StringField = "string#0", + SimpleStructField = new SimpleStruct("SimpleStructField#string#0", 5, DateTimeKind.Local), + SimpleStructProperty = new SimpleStruct("SimpleStructProperty#string#0", 2, DateTimeKind.Utc), + DT = new DateTime(2020, 1, 2, 3, 4, 5), + RGB = RGB.Blue + }; + Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, gs: {gs_local.StringField}, {vt_local.StringField}"); + } + + public static void TestStructsAsMethodArgs() + { + var ss_local = new SimpleStruct("ss_local#SimpleStruct#string#0", 5, DateTimeKind.Local); + var ss_ret = MethodWithStructArgs("TestStructsAsMethodArgs#label", ss_local, 3); + Console.WriteLine($"got back ss_local: {ss_local.gs.StringField}, ss_ret: {ss_ret.gs.StringField}"); + } + + static SimpleStruct MethodWithStructArgs(string label, SimpleStruct ss_arg, int x) + { + Console.WriteLine($"- ss_arg: {ss_arg.str_member}"); + ss_arg.Kind = DateTimeKind.Utc; + ss_arg.str_member = $"ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"; + ss_arg.gs.StringField = $"ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#{x}"; + return ss_arg; + } + + public static async Task MethodWithLocalStructsStaticAsync() + { + var ss_local = new SimpleStruct("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc); + var gs_local = new GenericStruct + { + StringField = "gs_local#GenericStruct#StringField", + List = new System.Collections.Generic.List { 5, 3 }, + Options = Options.Option2 + + }; + + var result = await ss_local.AsyncMethodWithStructArgs(gs_local); + Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, result: {result}"); + + return result; + } + + public string StringField; + public SimpleStruct SimpleStructProperty { get; set; } + public SimpleStruct SimpleStructField; + + public struct SimpleStruct + { + public uint V { get { return 0xDEADBEEF + (uint)dt.Month; } set { } } + public string str_member; + public DateTime dt; + public GenericStruct gs; + public DateTimeKind Kind; + + public SimpleStruct(string str, int f, DateTimeKind kind) + { + str_member = $"{str}#SimpleStruct#str_member"; + dt = new DateTime(2020 + f, 1 + f, 2 + f, 3 + f, 5 + f, 6 + f); + gs = new GenericStruct + { + StringField = $"{str}#SimpleStruct#gs#StringField", + List = new System.Collections.Generic.List { new DateTime(2010 + f, 2 + f, 3 + f, 10 + f, 2 + f, 3 + f) }, + Options = Options.Option1 + }; + Kind = kind; + } + + public Task AsyncMethodWithStructArgs(GenericStruct gs) + { + Console.WriteLine($"placeholder line for a breakpoint"); + if (gs.List.Count > 0) + return Task.FromResult(true); + + return Task.FromResult(false); + } + } + + public struct GenericStruct + { + public System.Collections.Generic.List List; + public string StringField; + + public Options Options { get; set; } + } + + public DateTime DT { get; set; } + public RGB RGB; + + public static void MethodWithLocalsForToStringTest(bool call_other) + { + var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); + decimal dec = 123987123; + var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014"); + + var dts = new DateTime[] + { + new DateTime(1983, 6, 7, 5, 6, 10), + new DateTime(1999, 10, 15, 1, 2, 3) + }; + + var obj = new ClassForToStringTests + { + DT = new DateTime(2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + + var sst = new StructForToStringTests + { + DT = new DateTime(2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + if (call_other) + MethodWithArgumentsForToStringTest(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); + } + + static void MethodWithArgumentsForToStringTest( + bool call_other, // not really used, just to help with using common code in the tests + DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, + Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) + { + Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + } + + public static async Task MethodWithLocalsForToStringTestAsync(bool call_other) + { + var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); + decimal dec = 123987123; + var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014"); + + var dts = new DateTime[] + { + new DateTime(1983, 6, 7, 5, 6, 10), + new DateTime(1999, 10, 15, 1, 2, 3) + }; + + var obj = new ClassForToStringTests + { + DT = new DateTime(2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + + var sst = new StructForToStringTests + { + DT = new DateTime(2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + if (call_other) + await MethodWithArgumentsForToStringTestAsync(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); + } + + static async Task MethodWithArgumentsForToStringTestAsync( + bool call_other, // not really used, just to help with using common code in the tests + DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, + Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) + { + Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + } + + public static void MethodUpdatingValueTypeMembers() + { + var obj = new ClassForToStringTests + { + DT = new DateTime(1, 2, 3, 4, 5, 6) + }; + var vt = new StructForToStringTests + { + DT = new DateTime(4, 5, 6, 7, 8, 9) + }; + Console.WriteLine($"#1"); + obj.DT = new DateTime(9, 8, 7, 6, 5, 4); + vt.DT = new DateTime(5, 1, 3, 7, 9, 10); + Console.WriteLine($"#2"); + } + + public static async Task MethodUpdatingValueTypeLocalsAsync() + { + var dt = new DateTime(1, 2, 3, 4, 5, 6); + Console.WriteLine($"#1"); + dt = new DateTime(9, 8, 7, 6, 5, 4); + Console.WriteLine($"#2"); + } + + public static void MethodUpdatingVTArrayMembers() + { + var ssta = new[] + { + new StructForToStringTests { DT = new DateTime(1, 2, 3, 4, 5, 6) } + }; + Console.WriteLine($"#1"); + ssta[0].DT = new DateTime(9, 8, 7, 6, 5, 4); + Console.WriteLine($"#2"); + } + } + + class ClassForToStringTests + { + public DateTime DT; + public DateTimeOffset DTO; + public TimeSpan TS; + public decimal Dec; + public Guid Guid; + } + + struct StructForToStringTests + { + public DateTime DT; + public DateTimeOffset DTO; + public TimeSpan TS; + public decimal Dec; + public Guid Guid; + } + + public enum RGB + { + Red, + Green, + Blue + } + + [Flags] + public enum Options + { + None = 0, + Option1 = 1, + Option2 = 2, + Option3 = 4, + + All = Option1 | Option3 + } } diff --git a/sdks/wasm/tests/debugger/dependency.cs b/sdks/wasm/tests/debugger/dependency.cs new file mode 100644 index 000000000000..05925f75aa8f --- /dev/null +++ b/sdks/wasm/tests/debugger/dependency.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Simple +{ + public class Complex + { + public int A { get; set; } + public string B { get; set; } + object c; + + public Complex(int a, string b) + { + A = a; + B = b; + this.c = this; + } + + public int DoStuff() + { + return DoOtherStuff(); + } + + public int DoOtherStuff() + { + return DoEvenMoreStuff() - 1; + } + + public int DoEvenMoreStuff() + { + return 1 + BreakOnThisMethod(); + } + + public int BreakOnThisMethod() + { + var x = A + 10; + c = $"{x}_{B}"; + + return x; + } + } +} diff --git a/support/serial.c b/support/serial.c index 74c54b0a21ce..859dee81502a 100644 --- a/support/serial.c +++ b/support/serial.c @@ -510,7 +510,13 @@ set_signal (int fd, MonoSerialSignal signal, gboolean value) expected = get_signal_code (signal); if (ioctl (fd, TIOCMGET, &signals) == -1) - return -1; + { + /* Return successfully for pseudo-ttys. */ + if (errno == EINVAL) + return 1; + + return -1; + } activated = (signals & expected) != 0; if (activated == value) /* Already set */