Is it always safe to delete unreachable code?

Suppose I have a tool that automatically deletes C # code detected by the compiler as unreachable. Is there a situation where such an operation can cause me problems? Please share interesting cases.

+5
source share
5 answers

Here is an interesting example. Consider the following function:

public static IEnumerable<int> Fun() { if (false) { yield return 0; } } 

A line with yield is defined as unreachable. However, deleting this file will make the program uncompiled. yield contained in this function gives the compiler information to reorganize the function, so it does nothing, it simply returns an empty collection. When you delete the yield line, it looks the same as a regular function without returning when it is required.

As in the commentary, this example is contrived, but instead of false we could have a constant value from another generated project, etc. (i.e. such a piece of code would not look as obvious as in this case).

Edit : Note: the yield construct is actually very similar to async/await . However, with the latest creators of the language, a different approach (IMO) has been taken that prevents such scenarios. Iterator blocks can be defined in the same way (using some keyword in the function signature instead of detecting it from the function body).

+13
source

I would not do this automatically, for the reasons mentioned in other answers, but I will say here that I will have a strong bias towards deleting unused code due to its preservation. After all, keeping track of legacy code is something that is for version control.

+3
source

This example is contrived, but it will be marked for deletion (in the default settings DEBUG ) and during the deletion various types of behavior are created.

 public class Baz { } public class Foo { public void Bar() { if (false) { // Will be flagged as unreachable code var baz = new Baz(); } var true_in_debug_false_in_release = GetType() .GetMethod("Bar") .GetMethodBody() .LocalVariables .Any(x => x.LocalType == typeof(Baz)); Console.WriteLine(true_in_debug_false_in_release); } } 

In Release mode (with default settings) the "unreachable code" will be optimized and will give the same result as if you deleted the if block in DEBUG mode.

Unlike the example using yield , this code compiles regardless of whether the invalid code is deleted or inaccessible.

+3
source

In addition to Dan Bryant ’s answer , here is an example of a program whose behavior will be changed by a tool that is smarter than the C # compiler in finding and deleting unreachable code:

 using System; class Program { static bool tru = true; static void Main(string[] args) { var x = new Destructive(); while (tru) { GC.Collect(2); } GC.KeepAlive(x); // unreachable yet has an effect on program output } } class Destructive { ~Destructive() { Console.WriteLine("Blah"); } } 

The C # compiler is not really trying to prove that GC.KeepAlive unavailable, so it does not GC.KeepAlive it in this case. As a result, the program will loop forever without printing anything.

If the tool proves that it is actually inaccessible (quite simple in this example) and deletes it, the behavior of the program changes. He will print "Blah" immediately, and then the cycle forever. So she became another program. Try if you have doubts; just comment out this unreachable line and look at the behavior change.

If GC.KeepAlive was for some reason, then this change would, in fact, be unsafe, and at some point the program may erroneously (possibly just crash).

+3
source

One rare borderline is if the unreachable code contains a call to GC.KeepAlive . This is very rare (since it is related to specific random use cases of unmanaged / managed interaction), but if you interact with unmanaged code that requires it, deleting it can lead to intermittent crashes if you are unlucky to have a GC trigger in the most Wrong moment.


UPDATE:

I have tested this and I can confirm that Servy is correct; the call to GC.KeepAlive does not take effect because the compiler proves that it can never use the link. This is due not only to the fact that the method never executes (the method should not execute it so that it affects the behavior of the GC), but because the compiler ignores GC.KeepAlive when this is impossible to achieve.

I leave this answer because it is still interesting for a counter business. This is a mechanism for hacking a program if you modify your code to make it inaccessible, but do not move GC.KeepAlive to make sure that it still maintains the link.

0
source

Source: https://habr.com/ru/post/1203311/


All Articles