Browse Source

Add support for decompiling query expressions.

pull/118/head
Daniel Grunwald 14 years ago
parent
commit
83489b2cc8
  1. 17
      ICSharpCode.Decompiler/Ast/DecompilerContext.cs
  2. 43
      ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs
  3. 271
      ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs
  4. 6
      ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs
  5. 12
      ICSharpCode.Decompiler/DecompilerSettings.cs
  6. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  7. 1
      ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
  8. 113
      ICSharpCode.Decompiler/Tests/QueryExpressions.cs
  9. 1
      ILSpy/DecompilerSettingsPanel.xaml
  10. 2
      ILSpy/DecompilerSettingsPanel.xaml.cs

17
ICSharpCode.Decompiler/Ast/DecompilerContext.cs

@ -4,6 +4,9 @@ @@ -4,6 +4,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using ICSharpCode.Decompiler.Ast;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using Mono.Cecil;
namespace ICSharpCode.Decompiler
@ -16,11 +19,25 @@ namespace ICSharpCode.Decompiler @@ -16,11 +19,25 @@ namespace ICSharpCode.Decompiler
public MethodDefinition CurrentMethod;
public DecompilerSettings Settings = new DecompilerSettings();
// public ITypeResolveContext TypeResolveContext;
// public IProjectContent ProjectContent;
public DecompilerContext(ModuleDefinition currentModule)
{
if (currentModule == null)
throw new ArgumentNullException("currentModule");
this.CurrentModule = currentModule;
// this.ProjectContent = new CecilTypeResolveContext(currentModule);
// List<ITypeResolveContext> resolveContexts = new List<ITypeResolveContext>();
// resolveContexts.Add(this.ProjectContent);
// foreach (AssemblyNameReference r in currentModule.AssemblyReferences) {
// AssemblyDefinition d = currentModule.AssemblyResolver.Resolve(r);
// if (d != null) {
// resolveContexts.Add(new CecilTypeResolveContext(d.MainModule));
// }
// }
// this.TypeResolveContext = new CompositeTypeResolveContext(resolveContexts);
}
/// <summary>

43
ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil;
namespace ICSharpCode.Decompiler.Ast.Transforms
{
/// <summary>
/// Converts extension method calls into infix syntax.
/// </summary>
public class IntroduceExtensionMethods : IAstTransform
{
readonly DecompilerContext context;
public IntroduceExtensionMethods(DecompilerContext context)
{
this.context = context;
}
public void Run(AstNode compilationUnit)
{
foreach (InvocationExpression invocation in compilationUnit.Descendants.OfType<InvocationExpression>()) {
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression;
MethodReference methodReference = invocation.Annotation<MethodReference>();
if (mre != null && mre.Target is TypeReferenceExpression && methodReference != null && invocation.Arguments.Any()) {
MethodDefinition d = methodReference.Resolve();
if (d != null) {
foreach (var ca in d.CustomAttributes) {
if (ca.AttributeType.Name == "ExtensionAttribute" && ca.AttributeType.Namespace == "System.Runtime.CompilerServices") {
mre.Target = invocation.Arguments.First().Detach();
mre.TypeArguments.Clear();
break;
}
}
}
}
}
}
}
}

271
ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs

@ -0,0 +1,271 @@ @@ -0,0 +1,271 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
namespace ICSharpCode.Decompiler.Ast.Transforms
{
/// <summary>
/// Decompiles query expressions.
/// Based on C# 4.0 spec, §7.16.2 Query expression translation
/// </summary>
public class IntroduceQueryExpressions : IAstTransform
{
readonly DecompilerContext context;
public IntroduceQueryExpressions(DecompilerContext context)
{
this.context = context;
}
public void Run(AstNode compilationUnit)
{
if (!context.Settings.QueryExpressions)
return;
DecompileQueries(compilationUnit);
foreach (QueryExpression query in compilationUnit.Descendants.OfType<QueryExpression>()) {
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
if (IsDegenerateQuery(query)) {
// introduce select for degenerate query
query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(fromClause.Identifier) });
}
QueryExpression innerQuery = fromClause.Expression as QueryExpression;
while (IsDegenerateQuery(innerQuery)) {
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
if (fromClause.Identifier != innerFromClause.Identifier)
break;
// Replace the fromClause with all clauses from the inner query
fromClause.Remove();
QueryClause insertionPos = null;
foreach (var clause in innerQuery.Clauses) {
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
}
fromClause = innerFromClause;
innerQuery = fromClause.Expression as QueryExpression;
}
}
}
bool IsDegenerateQuery(QueryExpression query)
{
if (query == null)
return false;
var lastClause = query.Clauses.LastOrDefault();
return !(lastClause is QuerySelectClause || lastClause is QueryGroupClause);
}
void DecompileQueries(AstNode node)
{
QueryExpression query = DecompileQuery(node as InvocationExpression);
for (AstNode child = (query ?? node).FirstChild; child != null; child = child.NextSibling) {
DecompileQueries(child);
}
}
QueryExpression DecompileQuery(InvocationExpression invocation)
{
if (invocation == null)
return null;
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression;
if (mre == null)
return null;
switch (mre.MemberName) {
case "Select":
{
if (invocation.Arguments.Count != 1)
return null;
string parameterName;
Expression body;
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) {
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() });
query.Clauses.Add(new QuerySelectClause { Expression = body.Detach() });
invocation.ReplaceWith(query);
return query;
}
return null;
}
case "GroupBy":
{
if (invocation.Arguments.Count != 2)
return null;
string parameterName1, parameterName2;
Expression keySelector, elementSelector;
if (MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName1, out keySelector)
&& MatchSimpleLambda(invocation.Arguments.ElementAt(1), out parameterName2, out elementSelector)
&& parameterName1 == parameterName2)
{
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = parameterName1, Expression = mre.Target.Detach() });
query.Clauses.Add(new QueryGroupClause { Projection = elementSelector.Detach(), Key = keySelector.Detach() });
invocation.ReplaceWith(query);
return query;
}
return null;
}
case "SelectMany":
{
if (invocation.Arguments.Count != 2)
return null;
string parameterName;
Expression collectionSelector;
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName, out collectionSelector))
return null;
LambdaExpression lambda = invocation.Arguments.ElementAt(1) as LambdaExpression;
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) {
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
if (p1.Name == parameterName) {
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = p1.Name, Expression = mre.Target.Detach() });
query.Clauses.Add(new QueryFromClause { Identifier = p2.Name, Expression = collectionSelector.Detach() });
query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() });
invocation.ReplaceWith(query);
return query;
}
}
return null;
}
case "Where":
{
if (invocation.Arguments.Count != 1)
return null;
string parameterName;
Expression body;
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) {
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() });
query.Clauses.Add(new QueryWhereClause { Condition = body.Detach() });
invocation.ReplaceWith(query);
return query;
}
return null;
}
case "OrderBy":
case "OrderByDescending":
case "ThenBy":
case "ThenByDescending":
{
if (invocation.Arguments.Count != 1)
return null;
string parameterName;
Expression orderExpression;
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out orderExpression)) {
if (ValidateThenByChain(invocation, parameterName)) {
QueryOrderClause orderClause = new QueryOrderClause();
InvocationExpression tmp = invocation;
while (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") {
// insert new ordering at beginning
orderClause.Orderings.InsertAfter(
null, new QueryOrdering {
Expression = orderExpression.Detach(),
Direction = (mre.MemberName == "ThenBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
});
tmp = (InvocationExpression)mre.Target;
mre = (MemberReferenceExpression)tmp.Target;
MatchSimpleLambda(tmp.Arguments.Single(), out parameterName, out orderExpression);
}
// insert new ordering at beginning
orderClause.Orderings.InsertAfter(
null, new QueryOrdering {
Expression = orderExpression.Detach(),
Direction = (mre.MemberName == "OrderBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
});
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() });
query.Clauses.Add(orderClause);
invocation.ReplaceWith(query);
return query;
}
}
return null;
}
case "Join":
case "GroupJoin":
{
if (invocation.Arguments.Count != 4)
return null;
Expression source1 = mre.Target;
Expression source2 = invocation.Arguments.ElementAt(0);
string elementName1, elementName2;
Expression key1, key2;
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(1), out elementName1, out key1))
return null;
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(2), out elementName2, out key2))
return null;
LambdaExpression lambda = invocation.Arguments.ElementAt(3) as LambdaExpression;
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) {
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
if (p1.Name == elementName1 && (p2.Name == elementName2 || mre.MemberName == "GroupJoin")) {
QueryExpression query = new QueryExpression();
query.Clauses.Add(new QueryFromClause { Identifier = elementName1, Expression = source1.Detach() });
QueryJoinClause joinClause = new QueryJoinClause();
joinClause.JoinIdentifier = elementName2; // join elementName2
joinClause.InExpression = source2.Detach(); // in source2
joinClause.OnExpression = key1.Detach(); // on key1
joinClause.EqualsExpression = key2.Detach(); // equals key2
if (mre.MemberName == "GroupJoin") {
joinClause.IntoIdentifier = p2.Name; // into p2.Name
}
query.Clauses.Add(joinClause);
query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() });
invocation.ReplaceWith(query);
return query;
}
}
return null;
}
default:
return null;
}
}
/// <summary>
/// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation.
/// </summary>
bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName)
{
if (invocation == null || invocation.Arguments.Count != 1)
return false;
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression;
if (mre == null)
return false;
string parameterName;
Expression body;
if (!MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body))
return false;
if (parameterName != expectedParameterName)
return false;
if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending")
return true;
else if (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending")
return ValidateThenByChain(mre.Target as InvocationExpression, expectedParameterName);
else
return false;
}
/// <summary>Matches simple lambdas of the form "a => b"</summary>
bool MatchSimpleLambda(Expression expr, out string parameterName, out Expression body)
{
LambdaExpression lambda = expr as LambdaExpression;
if (lambda != null && lambda.Parameters.Count == 1 && lambda.Body is Expression) {
ParameterDeclaration p = lambda.Parameters.Single();
if (p.ParameterModifier == ParameterModifier.None) {
parameterName = p.Name;
body = (Expression)lambda.Body;
return true;
}
}
parameterName = null;
body = null;
return false;
}
}
}

6
ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs

@ -9,7 +9,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -9,7 +9,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
{
public interface IAstTransform
{
void Run(AstNode node);
void Run(AstNode compilationUnit);
}
public static class TransformationPipeline
@ -25,7 +25,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -25,7 +25,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
new AddCheckedBlocks(),
new DeclareVariables(context), // should run after most transforms that modify statements
new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables
new IntroduceUsingDeclarations(context)
new IntroduceUsingDeclarations(context),
new IntroduceExtensionMethods(context), // must run after IntroduceUsingDeclarations
new IntroduceQueryExpressions(context), // must run after IntroduceExtensionMethods
};
}

12
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -140,6 +140,18 @@ namespace ICSharpCode.Decompiler @@ -140,6 +140,18 @@ namespace ICSharpCode.Decompiler
}
}
bool queryExpressions = true;
public bool QueryExpressions {
get { return queryExpressions; }
set {
if (queryExpressions != value) {
queryExpressions = value;
OnPropertyChanged("QueryExpressions");
}
}
}
bool fullyQualifyAmbiguousTypeNames = true;
public bool FullyQualifyAmbiguousTypeNames {

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -62,6 +62,8 @@ @@ -62,6 +62,8 @@
<Compile Include="Ast\Transforms\ConvertConstructorCallIntoInitializer.cs" />
<Compile Include="Ast\Transforms\DeclareVariables.cs" />
<Compile Include="Ast\Transforms\DelegateConstruction.cs" />
<Compile Include="Ast\Transforms\IntroduceExtensionMethods.cs" />
<Compile Include="Ast\Transforms\IntroduceQueryExpressions.cs" />
<Compile Include="Ast\Transforms\IntroduceUnsafeModifier.cs" />
<Compile Include="Ast\Transforms\IntroduceUsingDeclarations.cs" />
<Compile Include="Ast\Transforms\ReplaceMethodCallsWithOperators.cs" />

1
ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj

@ -55,6 +55,7 @@ @@ -55,6 +55,7 @@
<Compile Include="CallOverloadedMethod.cs" />
<Compile Include="CheckedUnchecked.cs" />
<Compile Include="IncrementDecrement.cs" />
<Compile Include="QueryExpressions.cs" />
<Compile Include="Switch.cs" />
<Compile Include="UnsafeCode.cs" />
<Compile Include="Types\S_TypeDeclarations.cs" />

113
ICSharpCode.Decompiler/Tests/QueryExpressions.cs

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Linq;
public class QueryExpressions
{
public class Customer
{
public int CustomerID;
public IEnumerable<Order> Orders;
public string Name;
public string Country;
}
public class Order
{
public int OrderID;
public DateTime OrderDate;
public Customer Customer;
public int CustomerID;
public decimal Total;
public IEnumerable<OrderDetail> Details;
}
public class OrderDetail
{
public decimal UnitPrice;
public int Quantity;
}
public IEnumerable<Customer> customers;
public IEnumerable<Order> orders;
public object MultipleWhere()
{
return
from c in this.customers
where c.Orders.Count() > 10
where c.Country == "DE"
select c;
}
public object MultipleFromFollowedBySelect()
{
return
from c in this.customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total };
}
public object MultipleFromFollowedByOrderBy()
{
return
from c in this.customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total };
}
public object FromLetWhereSelect()
{
return
from o in this.orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t };
}
public object MultipleLet()
{
return
from a in this.customers
let b = a.Country
let c = a.Name
select b + c;
}
public object Join()
{
return
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total };
}
public object JoinInto()
{
return
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n };
}
public object OrderBy()
{
return
from o in orders
orderby o.Customer.Name, o.Total descending
select o;
}
public object GroupBy()
{
return
from c in customers
group c.Name by c.Country;
}
}

1
ILSpy/DecompilerSettingsPanel.xaml

@ -5,5 +5,6 @@ @@ -5,5 +5,6 @@
<StackPanel Margin="10">
<CheckBox IsChecked="{Binding AnonymousMethods}">Decompile anonymous methods/lambdas</CheckBox>
<CheckBox IsChecked="{Binding YieldReturn}">Decompile enumerators (yield return)</CheckBox>
<CheckBox IsChecked="{Binding QueryExpressions}" IsEnabled="{Binding AnonymousMethods}">Decompile query expressions</CheckBox>
</StackPanel>
</UserControl>

2
ILSpy/DecompilerSettingsPanel.xaml.cs

@ -45,6 +45,7 @@ namespace ICSharpCode.ILSpy @@ -45,6 +45,7 @@ namespace ICSharpCode.ILSpy
DecompilerSettings s = new DecompilerSettings();
s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods;
s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn;
s.QueryExpressions = (bool?)e.Attribute("queryExpressions") ?? s.QueryExpressions;
return s;
}
@ -54,6 +55,7 @@ namespace ICSharpCode.ILSpy @@ -54,6 +55,7 @@ namespace ICSharpCode.ILSpy
XElement section = new XElement("DecompilerSettings");
section.SetAttributeValue("anonymousMethods", s.AnonymousMethods);
section.SetAttributeValue("yieldReturn", s.YieldReturn);
section.SetAttributeValue("queryExpressions", s.QueryExpressions);
XElement existingElement = root.Element("DecompilerSettings");
if (existingElement != null)

Loading…
Cancel
Save