From 7ac44375ff469f69f10be39558bba39843361df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Mon, 11 Apr 2011 22:50:41 +0100 Subject: [PATCH 1/3] Fixed NullReferenceException caused by unreachable IL code. It was generally caused by throw followed by catch leave. Closes #89. Closes #91. Closes #96. Closes #97. Closes #117. --- ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index df88a3f23..0410e4ff8 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -389,11 +389,13 @@ namespace ICSharpCode.Decompiler.ILAst } } + // Occasionally the compiler generates unreachable code - it can be usually just ignored + var reachableBody = body.Where(b => b.StackBefore != null); + var unreachableBody = body.Where(b => b.StackBefore == null); + // Genertate temporary variables to replace stack - foreach(ByteCode byteCode in body) { - if (byteCode.StackBefore == null) - continue; - + // Unrachable code does not need temporary variables - the values are never pushed on the stack for consuption + foreach(ByteCode byteCode in reachableBody) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { @@ -410,18 +412,20 @@ namespace ICSharpCode.Decompiler.ILAst } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) - foreach(ByteCode byteCode in body) { + // This has to be done after all temporary variables are assigned so we know about all loads + // Unrachable code will not have any StoreTo + foreach(ByteCode byteCode in reachableBody) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one - var loadedBy = locVars.Select(argVar => body.SelectMany(bc => bc.StackBefore).Where(s => s.LoadFrom == argVar).Single()).ToList(); + var loadedBy = locVars.Select(locVar => reachableBody.SelectMany(bc => bc.StackBefore).Where(s => s.LoadFrom == locVar).Single()).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.PushedBy.Length == 1 && slot.PushedBy[0] == byteCode)) { // Great - we can reduce everything into single variable ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; byteCode.StoreTo = new List() { tmpVar }; - foreach(ByteCode bc in body) { + foreach(ByteCode bc in reachableBody) { for (int i = 0; i < bc.StackBefore.Count; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { From 6222ef7bbf1c05ff9fb739718a09ecc3cd9f90b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Tue, 12 Apr 2011 00:31:39 +0100 Subject: [PATCH 2/3] Do stack analysis on the whole filter body. Closes #88 --- ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 23 +++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 0410e4ff8..21f94ff57 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -264,9 +264,11 @@ namespace ICSharpCode.Decompiler.ILAst // Add known states if(methodDef.Body.HasExceptionHandlers) { foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { - ByteCode handlerStart = instrToByteCode[ex.HandlerType == ExceptionHandlerType.Filter ? ex.FilterStart : ex.HandlerStart]; + ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new List(); + handlerStart.VariablesBefore = VariableSlot.MakeFullState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { + // Catch and Filter handlers start with the exeption on the stack ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, @@ -276,8 +278,23 @@ namespace ICSharpCode.Decompiler.ILAst ldexceptions[ex] = ldexception; handlerStart.StackBefore.Add(new StackSlot(ldexception)); } - handlerStart.VariablesBefore = VariableSlot.MakeFullState(varCount); agenda.Push(handlerStart); + + if (ex.HandlerType == ExceptionHandlerType.Filter) + { + ByteCode filterStart = instrToByteCode[ex.FilterStart]; + filterStart.StackBefore = new List(); + filterStart.VariablesBefore = VariableSlot.MakeFullState(varCount); + ByteCode ldexception = new ByteCode() { + Code = ILCode.Ldexception, + Operand = ex.CatchType, + PopCount = 0, + PushCount = 1 + }; + // TODO: ldexceptions[ex] = ldexception; + filterStart.StackBefore.Add(new StackSlot(ldexception)); + agenda.Push(filterStart); + } } } @@ -493,7 +510,7 @@ namespace ICSharpCode.Decompiler.ILAst Variable = new ILVariable() { Name = "var_" + variableIndex, Type = isPinned ? ((PinnedType)varType).ElementType : varType, - OriginalVariable = methodDef.Body.Variables[variableIndex] + OriginalVariable = methodDef.Body.Variables[variableIndex] }, Stores = stores, Loads = loads From 08b8645308bf792b29d6db4ee920c3a37531985a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Tue, 12 Apr 2011 01:59:46 +0100 Subject: [PATCH 3/3] Handle uninitialized variables. Closes #102. Closes #116. --- ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 21f94ff57..f9394fbce 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -527,11 +527,30 @@ namespace ICSharpCode.Decompiler.ILAst Loads = new List() }).ToList(); + // VB.NET uses the 'init' to allow use of uninitialized variables. + // We do not really care about them too much - if the original variable + // was uninitialized at that point it means that no store was called and + // thus all our new variables must be uninitialized as well. + // So it does not matter which one we load. + + // TODO: We should add explicit initialization so that C# code compiles. + // Remember to handle cases where one path inits the variable, but other does not. + // Add loads to the data structure; merge variables if necessary foreach(ByteCode load in loads) { ByteCode[] storedBy = load.VariablesBefore[variableIndex].StoredBy; if (storedBy.Length == 0) { - throw new Exception("Load of uninitialized variable"); + // Load which always loads the default ('uninitialized') value + // Create a dummy variable just for this load + newVars.Add(new VariableInfo() { + Variable = new ILVariable() { + Name = "var_" + variableIndex + "_" + load.Offset.ToString("X2") + "_default", + Type = varType, + OriginalVariable = methodDef.Body.Variables[variableIndex] + }, + Stores = new List(), + Loads = new List() { load } + }); } else if (storedBy.Length == 1) { VariableInfo newVar = newVars.Where(v => v.Stores.Contains(storedBy[0])).Single(); newVar.Loads.Add(load);