Skip to content

[hot reload][coreclr] cumulative updates to properties in generic classes crash the runtme. #85605

Closed
@lambdageek

Description

@lambdageek

Applying cumulative updates to properties in generic classes seems to shutdown coreclr.

In effect this is the code before the first update:

            using System;
            namespace testProperty;
            public class C1<T> {
                public T MyProp1 {get; set;}

                public static int M1() {
                    Console.WriteLine("Hello");
                    return 17;
                }
            }

Then the update changes it to:

                using System;
                namespace testProperty;
                public class C1<T> {
                    public T MyProp1 {get; set;}

                    public T MyProp2 {
                        get => default(T);
                        set {
                            Console.WriteLine($"setting MyProp2 to {value}");
                        }
                    }

                    public static int M1() {
                        var x = new C1<string>();
                        x.MyProp1 = "hello1";
                        x.MyProp2 = "hello2";
                        return 42;
                    }
                }

And finally change it to:

                using System;
                namespace testProperty;
                public class C1<T> {
                    public T MyProp1 {get; set;}

                    public T MyProp2 {
                        get => default(T);
                        set {
                            Console.WriteLine($"updated setting MyProp2 to {value}");
                        }
                    }
                    public T[] MyProp3 {
                        get => default(T[]);
                        set {
                            Console.WriteLine($"setting MyProp3 to {value}");
                        }
                    }

                    public static int M1() {
                        var x = new C1<string>();
                        x.MyProp1 = "hello1";
                        x.MyProp2 = "hello2";
                        x.MyProp3 = new string[] { "hello3" };
                        return 45;
                    }
                }

The actual invocations are done like this (ApplyUpdate is just loading the bytes from the .dmeta and .dil files and calling the System.Reflection.Metadata.MetadataUpdater.ApplyUpdate method):

        int x = testProperty.C1<int>.M1();
        ApplyUpdate();
        x = testProperty.C1<string>.M1();
        if (x != 42) {
            throw new Exception("Expected 42, got " + x);
        }
        PropertyInfo pInfo = typeof(testProperty.C1<string>).GetProperty("MyProp2");
        if (pInfo == null) {
            throw new Exception("Can't find newly added property: MyProp2");
        }
        ApplyUpdate(2);
        Console.WriteLine("Applied second update");
        x = testProperty.C1<string>.M1();
        if (x != 45) {
            throw new Exception("Expected 45, got " + x);
        }
        pInfo = typeof(testProperty.C1<string>).GetProperty("MyProp3");
        if (pInfo == null) {
            throw new Exception("Can't find newly added property: MyProp3");
        }
        MethodInfo mInfo = pInfo.GetGetMethod();
        if (mInfo == null) {
            throw new Exception("Can't find newly added property getter: MyProp3");
        }

I'm including a zip file with a .dll and .dmeta and .dil files with the changes. Use the following runner program to run it after unzipping the test case into some directory.

reproProps.zip

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;

if (args.Length != 1)
{
    Console.WriteLine("Usage: generic-runner <directory>");
    return 1;
}

string directory = args[0];

if (!Directory.Exists(directory))
{
    Console.WriteLine("Directory does not exist: {0}", directory);
    return 1;
}


foreach (var subdir in Directory.GetDirectories(directory))
{
    var alc = System.Runtime.Loader.AssemblyLoadContext.Default;

    var assemblies = Directory.GetFiles(subdir, "*.dll");
    foreach (var assembly in assemblies)
    {
        Console.WriteLine ($">>> {assembly}");
        var asm = alc.LoadFromAssemblyPath(assembly);
        var type = asm.GetType("HotReloadTest", throwOnError: false);
        if (type == null)
            continue;
        var mi = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
        if (mi == null)
            continue;
        Console.WriteLine("====================================================");
        Console.WriteLine($"Running {asm.GetName().Name}");
        var passed = false;
        try
        {
            mi.Invoke(null, null);
            passed = true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
        if (passed)
            Console.WriteLine($"Passed {asm.GetName().Name}");
    }
}

return 0;

Expected result:

$ MONO_ENV_OPTIONS=--interp DOTNET_MODIFIABLE_ASSEMBLIES=debug dotnet run -- /tmp/reproProps
>>> /tmp/reproProps/testProperty/testProperty.dll
====================================================
Running testProperty
 --- [testProperty]: Running test
Hello
 --- [testProperty]: Applying update 1
setting MyProp2 to hello2
 --- [testProperty]: Applying update 2
Applied second update
updated setting MyProp2 to hello2
setting MyProp3 to System.String[]
Passed testProperty

Actual result:

$ DOTNET_MODIFIABLE_ASSEMBLIES=debug dotnet run -- /tmp/reproProps
>>> /tmp/reproProps/testProperty/testProperty.dll
====================================================
Running testProperty
 --- [testProperty]: Running test
Hello
 --- [testProperty]: Applying update 1
setting MyProp2 to hello2
 --- [testProperty]: Applying update 2

Version info:

I'm running a "main" installer from https://github.com/dotnet/installer. 8.0.100-preview.5.23228.7

% dotnet --info
.NET SDK:
 Version:   8.0.100-preview.5.23228.7
 Commit:    0ce891843a

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  13.3
 OS Platform: Darwin
 RID:         osx.13-arm64
 Base Path:   /Users/alklig/work/net8-playground/dotnet/sdk/8.0.100-preview.5.23228.7/

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.0-preview.4.23225.14
  Architecture: arm64
  Commit:       9a7db5556f

.NET SDKs installed:
  8.0.100-preview.4.23225.3 [/Users/alklig/work/net8-playground/dotnet/sdk]
  8.0.100-preview.4.23228.11 [/Users/alklig/work/net8-playground/dotnet/sdk]
  8.0.100-preview.5.23228.7 [/Users/alklig/work/net8-playground/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 8.0.0-preview.4.23224.5 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.0-preview.4.23225.8 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.0-preview.5.23226.1 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 8.0.0-preview.4.23224.7 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.0-preview.4.23225.10 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.0-preview.4.23225.14 [/Users/alklig/work/net8-playground/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  DOTNET_ROOT       [/Users/alklig/work/net8-playground/dotnet]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions