JustDecompile – One More Time

I’ve looked at JustDecompile a few times over the past year, following its progress from its early beta stages.  When I saw that it was officially released yesterday (14 Feb 2012) I remembered telling Vladimir Dragoev from the JustDecompile team that I’d give it another look.  I’ve been very critical of this product in the past but given that everything I’ve looked at so far has been pre-release software it’s only fair that I give it one more chance to redeem itself.  Can this version finally handle my tests?

The Journey

When I first looked at JustDecompile last May (v2011.1.516.2) I had really high hopes for it but back then its output was far from acceptable.  Many of my tests resulted in code that in no way resembled the original source and more often than not the result wasn’t even valid C#.  To top it off, the application crashed frequently in easily reproducible ways.  Given what I observed I had no choice but to call it unusable.  That said, it was a beta so I provided some feedback to the team and moved on.

My second look was in September (v2011.1.829.2).  The team had been working on fixing defects and I’d seen several notices that the issues I’d reported were either fixed or being fixed.  This seemed like as good of a time as any to give it another chance.  The first thing I noticed was that the application no longer crashed with the predictability I’d noticed in May.  When I reran my original tests I found that although the code it generated had certainly improved (sometimes to the point of being almost identical to the original source) it still had a number of issues that ultimately left me saying it still wasn’t a viable alternative to the other solutions already available.

For this review I’m using version 2012.1.214.2.  When I read about some of the new features in this release I was pretty surprised to see that Telerik had completely rebuilt the decompilation engine.  For consistency across all three reviews I’ll be sticking with my original series of tests so we’ll see how well the new version fares.

User Interface

I didn’t notice many changes in the UI from the last review.  From what I remember, most everything is in the same place.  The stand-out feature here has to be the Create Project button.

Create Project (also accessible from a context menu) lets us create a new Visual Studio project from a decompiled assembly.  For my simple project I found that although the formatting left something to be desired the generated project was adequate.  I did have to remove the generated _Module_.cs file from the project to get it to compile but otherwise it worked fine.  I can see this being a useful tool for recovering lost code.

Note: Create Project only supports creating projects in C#.  If Visual Basic or IL are selected the button will be disabled.

Multiple Node Selections

Multiple Node Selections

I found navigating the assembly panel a but frustrating due to some inconsistencies.  For example, double-clicking will expand or collapse  assembly and namespace nodes  but not individual types.  I also somehow managed to get multiple nodes to appear as selected.  Sometimes collapsing the parent node would clear the selection, other times it wouldn’t.  At one point I even managed to crash JustDecompile just clicking around in the assembly panel but I couldn’t reproduce it.

Code Tests

As I mentioned earlier, this version has a completely rewritten decompilation engine.  Decompilation had come a long way between my last two looks so I was really interested in whether the new engine closed the gap between some of the competing products.  I have to say that this version surprised me a bit.

First, it got most of the decompilations correct – including the conditional operator tests that it choked on before.  What surprised me even more though was some of the code it produced.  The code wasn’t necessarily wrong per se, just…unusual.  Let’s take a look.

Just like before we’ll start with the switch statements.  I’ll be pointing out some of the oddities as we encounter them.  Also assume the following enumeration for all code samples:

public enum SampleEnum
{
	Unknown,
	Item1,
	Item2
}

Switch Statements

The last version I looked at did really well with the switch statements.  It had really come a long way from including MSIL labels and misplacing case bodies.  Although I’m pleased to report that the released version also handles each test correctly I think in some cases quality of the emitted code has taken a step backwards. Take the first test as an example.

In this test I simply return a string from each case:

public string SampleMethod(SampleEnum enumValue)
{
	switch (enumValue)
	{
		case SampleEnum.Item1:
			return "Item 1";

		case SampleEnum.Item2:
			return "Item 2";

		case SampleEnum.Unknown:
			return "Unknown";
	}

	return String.Empty;
}

JustDecompile produces code that is functionally equivalent but introduces a default case:

public string SampleMethod(SampleEnum enumValue)
{
	string empty;
	SampleEnum sampleEnum = enumValue;
	switch (sampleEnum)
	{
		case SampleEnum.Unknown:
		{
			empty = "Unknown";
			break;
		}
		case SampleEnum.Item1:
		{
			empty = "Item 1";
			break;
		}
		case SampleEnum.Item2:
		{
			empty = "Item 2";
			break;
		}
		default:
		{
			empty = string.Empty;
			break;
		}
	}
	return empty;
}

Interestingly, this code from JustDecompile is almost identical to what ILSpy produces. I’m guessing that’s not coincidental since they’re both based on Cecil. Notice I said that the output is almost identical to what ILSpy produces though. The one thing that JustDecompile’s output has that ILSpy’s doesn’t is line 4 – SampleEnum sampleEnum = enumValue;. Again, it’s not necessarily wrong, I don’t understand why it’s there.

The second switch test by adding a variable to store the string from each case rather than returning the string:

public string SampleMethod(SampleEnum enumValue)
{
	var val = String.Empty;

	switch (enumValue)
	{
		case SampleEnum.Item1:
			val = "Item 1";
			break;

		case SampleEnum.Item2:
			val = "Item 2";
			break;

		case SampleEnum.Unknown:
			val = "Unknown";
			break;
	}

	return val;
}

Again, JustDecompile’s result is acceptable:

public string SampleMethod(SampleEnum enumValue)
{
	string val = string.Empty;
	SampleEnum sampleEnum = enumValue;
	switch (sampleEnum)
	{
		case SampleEnum.Unknown:
		{
			val = "Unknown";
			break;
		}
		case SampleEnum.Item1:
		{
			val = "Item 1";
			break;
		}
		case SampleEnum.Item2:
		{
			val = "Item 2";
			break;
		}
	}
	string str = val;
	return str;
}

This decompilation is also functionally correct but this time not only do we have the extra enum declaration and assignment, we also have an extra string declaration and assignment. These variables weren’t present the last time I tried JustDecompile and they’re not present in ILSpy’s output either. They’re unnecessary and I’m not sure where they’re coming from. We see this pattern repeat for the next two tests.

Switch with default test:

public string SampleMethod(SampleEnum enumValue)
{
	var val = String.Empty;

	switch (enumValue)
	{
		case SampleEnum.Item1:
			val = "Item 1";
			break;

		case SampleEnum.Item2:
			val = "Item 2";
			break;

		default:
			val = "Unknown";
			break;
	}

	return val;
}

JustDecompile Result:

public string SampleMethod(SampleEnum enumValue)
{
	string val = string.Empty;
	SampleEnum sampleEnum = enumValue;
	switch (sampleEnum)
	{
		case SampleEnum.Item1:
		{
			val = "Item 1";
			break;
		}
		case SampleEnum.Item2:
		{
			val = "Item 2";
			break;
		}
		default:
		{
			val = "Unknown";
			break;
		}
	}
	string str = val;
	return str;
}

Switch with different default case body:

public string SampleMethod(SampleEnum enumValue)
{
	var val = String.Empty;

	switch (enumValue)
	{
		case SampleEnum.Item1:
			val = "Item 1";
			break;

		case SampleEnum.Item2:
			val = "Item 2";
			break;

		default:
			Console.WriteLine("Unknown");
			break;
	}

	return val;
}

JustDecompile Result:

public string SampleMethod(SampleEnum enumValue)
{
	string val = string.Empty;
	SampleEnum sampleEnum = enumValue;
	switch (sampleEnum)
	{
		case SampleEnum.Item1:
		{
			val = "Item 1";
			break;
		}
		case SampleEnum.Item2:
		{
			val = "Item 2";
			break;
		}
		default:
		{
			Console.WriteLine("Unknown");
			break;
		}
	}
	string str = val;
	return str;
}

Despite the extraneous variables the generated code is doing the right thing.

Conditional Operator

Saying JustDecompile has failed miserably on the conditional operator would be too generous. Before looking at what this version generates let’s review where we’re coming from.

The first test is a simple conditional check against a variable:

public void SampleMethod(int arg)
{
	var isEven = arg % 2 == 0;
	var val = isEven ? SampleEnum.Item1 : SampleEnum.Item2;
}

The last time I used JustDecompile it produced the following:

public void SampleMethod(int arg)
{
	bool isEven = arg % 2 == 0;
	SampleEnum val = isEven || SampleEnum.Item2;
}

As you can see, it got the variable assignment correct but that was about it. The code it generated wouldn’t even compile. This time around though I was surprised in more ways than one.

First, the team has taken a completely different approach by emitting an if/else rather than a conditional operator. This approach deviates significantly even from that ILSpy (which I noted in the last review was actually worse than what JustDecompile did). Furthermore, the code is actually, (finally!) functionally equivalent!

public void SampleMethod(int arg)
{
	byte num;
	bool isEven = arg % 2 == 0;
	if (isEven)
	{
		num = 1;
	}
	else
	{
		num = 2;
	}
	SampleEnum val = (SampleEnum)num;
}

It may not use a conditional operator but the result is the same.

The other test eliminated the isEven variable to flatten the expression:

public void SampleMethod(int arg)
{
	var val = arg % 2 == 0 ? SampleEnum.Item1 : SampleEnum.Item2;
}

JustDecompile also got it right too!

public void SampleMethod(int arg)
{
	byte num;
	if (arg % 2 == 0)
	{
		num = 1;
	}
	else
	{
		num = 2;
	}
	SampleEnum val = (SampleEnum)num;
}

As you can see, JustDecompile has come a long way when it comes to handling conditional operators.

Other Tests

Just as before I ran some older production code through JustDecompile. Although some of the code it produced left me scratching my head a bit (for the same reasons described above) JustDecompile performed much better this time around.

The first method I retested was my old IsNull method I wrote to check if a value in a DataRow was null before I knew about DataRow.IsNull:

public static bool IsNull(object value)
{
	return (value == null || value == DBNull.Value || value.ToString().Length == 0);
}

This time JustDecompile got it right. Of course, the generated code doesn’t look anything like the original and it introduces a really, really poorly named variable but at least the result is the same.

public static bool IsNull(object value)
{
	bool length;
	if (value == null || value == DBNull.Value)
	{
		length = true;
	}
	else
	{
		length = value.ToString().Length == 0;
	}
	bool flag = length;
	return flag;
}

Note: ILSpy’s output matches the original source almost exactly.

The other method I tested has had some serious problems in the past. For example, case blocks were mismatched or intermixed, and the code included MSIL variable names. The decompilation of that method still isn’t perfect but it seems to fail in the same ways as ILSpy. For instance, somewhere in the process an enumeration value initialized to 24 is misrepresented as 25. ILSpy handles this a little better since it tries to cast 25 to the enum but JustDecompile doesn’t.

The Bottom Line

I’ll readily admit that after having seen JustDecompile fail so miserably in the past I didn’t have very high expectations for this release. I’ve never expected a decompilation tool to generate code that matches all of my code verbatim (especially when considering any compiler optimizations) but in the past JustDecompile has generated code that in no way represented the original source. After seeing how well the rebuilt decompilation engine handled these tests I have to say that I’m impressed with how far the tool has come since I first looked at it.

Truth be told though, despite the improvements JustDecompile still feels a bit rough around the edges. As I said, I still managed to crash it once and some of the generated code is a bit questionable.  Overall though, I think it now has it’s place in the toolkit if only for getting a second opinion about how an assembly works.

As a final note, I recently learned about dotPeek, a free decompilation tool from JetBrains. I ran all of my tests against the version 1.0.0.7999 (early access program) and it handled them all perfectly. If you’re still mad at Red Gate and/or are looking for other decompilers, dotPeek looks like it’ll be a very good alternative.

Advertisements

2 comments

  1. Hi Dave!

    Thanks for the thorough feedback and your responsiveness. Your time and effort is much appreciated. It’s been JustDecompile’s philosophy to stay as close to the MSIL in the input assemblies as possible. When you compile something in Debug the MSIL produced by the compiler is similar to the C# produced by JustDecompile. If you compile these assemblies in Release then the MSIL will closely resemble original code. In that case JustDecompile will produce much better result.

    But of course there are cases where we can do better no matter what kind the input assemblies are, and you have found such cases. So thanks once again and we’ll take into serious account your feedback. I’ll be more than happy if you give us another try after some time as we are confident on continuing to improve the decompilation besides adding new handy features.

    Vladi,
    The Telerik team

  2. Dave, did you test JustDecompile on Release configuration compiled assemblies? Looking at the code produced in your switch statement tests I’d say the chances are they were compiled in the VS Debug configuration.

    As Vladi said our philosophy is to stay as close to the MSIL as possible. So, when you compile something in Debug these extra variables are really there. Using JustDecompile you get to know what the compiler actually generated for you. We intentionally avoid optimizing the generated code since we believe that our users would be more interested in seeing what really is inside that assembly.

    What this means is that you’ll see a much nicer code if you use Release configuration compiled assemblies. There are a lot of weird looking code patterns in Debug configuration compiled assemblies since under the Debug configuration the MSIL is generated with debugging in mind.

Comments are closed.