Problems with unsafe C# code


In this article, I will show what problems unsafe code can cause and a couple of examples of how you can change the value of a constant, readonly field, and property without a set method.

I do not know how useful this article will be for you, but the code listings in it are simply blow my mind enjoy reading.

Listing 1

What do you think this code will output and what caused this behavior?

string str = "Strings are immutable!";

for(int i = 0; i < str.Length / 2; i++)
{
    fixed(char* strPtr = str)
    {
        char tmp = strPtr[i];
        strPtr[i] = str[str.Length - i - 1];
        strPtr[str.Length - i - 1] = tmp;
    }
}

Console.WriteLine("Strings are immutable!"); // Ответ вас удивит

It would seem that this is a stupid question, but when I ran this code, I was very surprised.

What the code outputs:

!elbatummi era sgnirtS

Yes, yes, check for yourself.

To understand why this happens, you need to know 3 things:

  • String interning

  • Basic memory management

  • String is a pointer to the first character

The answer is really simple! When we create a string at compile time, it is interned. A string is the address of the first character, not the character itself, but the address! This code changes the meaning of characters in memory cells, but the addresses remain the same. So when we try to output “Strings are immutable!” by referring to the interned string table, we get the address of the first character of the string, but the values ​​of these characters are completely different.

ILDasm.exe |  The metadata shows the interned string
ILDasm.exe | The metadata shows the interned string

The problem here is obvious, if such a line reversal is in imported library, then in the future, when trying to access the string “Strings are immutable!” we get a different result without knowing it. I do not know how plausible this scenario is, but I am sure that it will be difficult to catch such an error, to put it mildly.

But for this we do not even have to include unsafe code! Seriously, if you turn off unsafe code, but it is in a third-party library, then the program will run smoothly. The following listing is an example.

Listing 2

Let’s create a new project as a class library and connect it to the main project. I named the new project LibReverse and its only class LibReverseString. I have included unsafe code in this project and created a public Reverse method which is a copy of the string reversal from listing 1.

public class LibReverseString
{
    unsafe public static void Reverse(string str)
    {
        fixed (char* strPtr = str)
        {
            for (int i = 0; i < str.Length / 2; i++)
            {
                char tmp = strPtr[i];
                strPtr[i] = strPtr[str.Length - i - 1];
                strPtr[str.Length - i - 1] = tmp;
            }
        }
    }
}

In the Program.cs file (where our Main method is) I imported our library

using LibReverse;

And made a call to the Reverse method

static void Main(string[] args)
{
    string str = "Strings are immutable!";

    LibReverseString.Reverse(str);

    Console.WriteLine("Strings are immutable!");
}

// Output:
// !elbatummi era sgnirtS

Note that in the main project with the Main method, the unsafe code is not even included.

Listing 3. Expanded string behavior in other methods

Everywhere in Listing 3 call LibReverseString.Reverse(str) you can safely replace it with the following code and the result will not change:

fixed (char* strPtr = str)
{
    for (int i = 0; i < str.Length / 2; i++)
    {
        char tmp = strPtr[i];
        strPtr[i] = strPtr[str.Length - i - 1];
        strPtr[str.Length - i - 1] = tmp;
    }
}

I left the Reverse call from a third-party library for better readability of the code.

Watch the listing to the end to understand why there is strange behavior, I will express my assumptions at the end.

I’ve added a simple Foo method that just prints a string to the screen.

private static void Foo()
{
    string str = "Strings are immutable!";
    Console.WriteLine(str);
}

Listing 3.1: Now the Main method has the following form

unsafe static void Main(string[] args)
{
    string str = "Strings are immutable!";
    
    Foo();
    
    LibReverseString.Reverse(str);

    Foo();
}

// Output:
// Strings are immutable!
// !elbatummi era sgnirtS

Here you can see absolutely normal behavior of the code, but if you slightly change the code by removing the first call to Foo.

Listing 3.2: no first call to Foo

unsafe static void Main(string[] args)
{
    string str = "Strings are immutable!";
    
    LibReverseString.Reverse(str);
    
    Foo();
}

// Output:
// Strings are immutable!

We just removed the first call to Foo before calling Reverse, but Reverse didn’t work. Or did it work? I have a theory about the correctness of which I’m not sure, please answer in the comments if you know for sure.

AT Listing 3.1 when we call Foo for the first time, we do a JIT compilation that binds Foo’s local variable str to the interned string “Strings are immutable!”, and only then do we unwrap it.

AT Listing 3.2 Foo is called after we’ve expanded the string. The variable str local to Foo does not refer to the expanded interned string. In support of this theory, I can give the following listing 3.3.

Listing 3.3: pre-JIT compilation of the entire TestApp assembly.

unsafe static void Main(string[] args)
{
    // Pre-JIT-compilation
    foreach (var type in Assembly.Load("TestApp").GetTypes())
    {
        foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly |
                            BindingFlags.NonPublic |
                            BindingFlags.Public | BindingFlags.Instance |
                            BindingFlags.Static))
        {
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(method.MethodHandle);
        }
    }

    string str = "Strings are immutable!";

    LibReverseString.Reverse(str);

    Foo();
}

// Output:
// !elbatummi era sgnirtS

I got this code from Vitaly Liptchinsky from his article on forced JIT compilation (https://www.codeproject.com/Articles/31316/Pre-compile-pre-JIT-your-assembly-on-the-fly-or-tr)

Listing 4

It would seem that constants are non-replaceable data, but this code says otherwise.

class Program
{
    public const string PI = "3.14";
    
    unsafe static void Main(string[] args)
    {
        fixed (char* PIPtr = PI)
        {
            PIPtr[0] = '0';
        }
        Console.WriteLine(PI); // 0.14
    }

}

Actually no, let me remind you again, a string is an ADDRESS to the first character. And the address in this case is really constant. Although in fact, I think, it is assumed that you will not change the value of the constants either.

Visual Studio still shows the value as “3.14”.

Listing 4.1: If we add the Foo method and change Main

unsafe static void Main(string[] args)
{

    fixed (char* PIPtr = PI)
    {
        PIPtr[0] = '0';
    }
  
    Console.WriteLine(PI); // 0.14

    Foo();
}

private static void Foo()
{
    Console.WriteLine(PI); // 3.14
}

// Output:
// 0.14
// 3.14

Here is the same story as Listing 3. I won’t stretch the post, I’ll just say that if you do a pre-JIT compilation, then Foo will output exactly “0.14”.

Listing 5

Here we see an “immutable” class. We can’t change the Name that has the readonly keyword, can we? We can’t change the reference, but changing the value by reference is as easy as shelling pears.

public class ImmutablePerson
{
    
    public string Name { get; }

    public ImmutablePerson(string name)
    {
        Name = name;
    }
}
ImmutablePerson person = new ImmutablePerson("William");

Console.WriteLine(person.Name); // William

fixed (char* strPtr = person.Name)
{
    strPtr[0] = 'M';
    strPtr[1] = 'i';
    strPtr[2] = 'c';
    strPtr[3] = 'h';
    strPtr[4] = 'a';
    strPtr[5] = 'e';
    strPtr[6] = 'l';
}

Console.WriteLine(person.Name); // Michael

This code will work even if we make the Name property with an init accessor or even a readonly field:

public string Name { get; init; }
public readonly string Name;

Afterword

Thanks to everyone who read this article. If you haven’t learned anything new or useful, I hope I was able to surprise you a little.

In the article Xenia Moseenkovna (@kmoseenk) is well told about the immutability of strings, some of the information is taken from it – https://habr.com/en/company/otus/blog/676680/

I learned most of the knowledge about the .NET device in the book ‘CLR via C#’ by the author Jeffrey Richter.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *