2021-09-20 18:20:01 +02:00

194 lines
6.6 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using dnlib.DotNet;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.TreeView;
using dnSpy.Contracts.TreeView;
namespace dnSpy.Debugger.DotNet.Metadata {
readonly struct TreeViewUpdater {
readonly IDocumentTabService documentTabService;
readonly DynamicModuleDefDocument Document;
readonly ModuleDocumentNode ModuleNode;
readonly HashSet<uint> modifiedTypes;
readonly HashSet<uint>? loadedClassTokens;
readonly HashSet<TypeDef> checkedTypes;
readonly ModuleDocumentNode modNode;
public TreeViewUpdater(IDocumentTabService documentTabService, DynamicModuleDefDocument document, ModuleDocumentNode node, HashSet<uint> modifiedTypes, HashSet<uint>? loadedClassTokens) {
Debug.Assert(node.Document == document);
this.documentTabService = documentTabService ?? throw new ArgumentNullException(nameof(documentTabService));
Document = document ?? throw new ArgumentNullException(nameof(document));
ModuleNode = node ?? throw new ArgumentNullException(nameof(node));
this.modifiedTypes = new HashSet<uint>(modifiedTypes);
this.loadedClassTokens = loadedClassTokens;
checkedTypes = new HashSet<TypeDef>();
modNode = node;
}
public void Update() {
// If none of its children have been loaded, we can safely return without doing anything
if (ModuleNode.TreeNode.LazyLoading)
return;
if (loadedClassTokens is not null) {
foreach (var a in loadedClassTokens)
modifiedTypes.Add(a);
}
var tokensList = new List<uint>(modifiedTypes);
tokensList.Sort();
bool needRedecompile = false;
foreach (uint token in tokensList) {
var td = Document.ModuleDef!.ResolveToken(token) as TypeDef;
Debug2.Assert(td is not null);
if (td is null)
continue;
Update(td);
needRedecompile = true;
}
if (needRedecompile) {
// Force a re-decompile of every view that references this module. This could be
// optimized if necessary
documentTabService.RefreshModifiedDocument(Document);
}
}
List<TypeDef> GetNonCheckedTypeAndDeclaringTypes(TypeDef td) {
var list = new List<TypeDef>();
while (td is not null && !checkedTypes.Contains(td)) {
list.Add(td);
checkedTypes.Add(td);
td = td.DeclaringType;
}
// Enclosing types before enclosed types
list.Reverse();
return list;
}
void Update(TypeDef td) {
var list = GetNonCheckedTypeAndDeclaringTypes(td);
CreateTypeNodes(list);
}
void CreateTypeNodes(List<TypeDef> types) {
TypeNode? parentNode = null;
foreach (var type in types) {
bool wasLoaded = loadedClassTokens?.Contains(type.MDToken.Raw) ?? false;
TypeNode typeNode;
if (type.DeclaringType is null)
typeNode = GetOrCreateNonNestedTypeTreeNode(modNode, type);
else {
if (parentNode is null)
parentNode = documentTabService.DocumentTreeView.FindNode(type.DeclaringType);
if (parentNode is null || parentNode.TreeNode.LazyLoading)
break;
typeNode = GetOrCreateNestedTypeTreeNode(parentNode, type);
}
Debug2.Assert(typeNode is not null);
if (wasLoaded || modifiedTypes.Contains(type.MDToken.Raw))
UpdateMemberNodes(typeNode);
parentNode = typeNode;
}
}
static TypeNode GetOrCreateNonNestedTypeTreeNode(ModuleDocumentNode modNode, TypeDef type) {
Debug2.Assert(type is not null && type.DeclaringType is null);
modNode.TreeNode.EnsureChildrenLoaded();
TypeNode? typeNode;
var nsNode = GetOrCreateNamespaceNode(modNode, type.Namespace);
typeNode = nsNode.TreeNode.DataChildren.OfType<TypeNode>().FirstOrDefault(a => a.TypeDef == type);
if (typeNode is not null)
return typeNode;
typeNode = nsNode.Create(type);
nsNode.TreeNode.AddChild(typeNode.TreeNode);
return typeNode;
}
static NamespaceNode GetOrCreateNamespaceNode(ModuleDocumentNode modNode, string ns) {
modNode.TreeNode.EnsureChildrenLoaded();
var nsNode = modNode.TreeNode.DataChildren.OfType<NamespaceNode>().FirstOrDefault(a => a.Name == ns);
if (nsNode is not null)
return nsNode;
nsNode = modNode.Create(ns);
modNode.TreeNode.AddChild(nsNode.TreeNode);
return nsNode;
}
static TypeNode GetOrCreateNestedTypeTreeNode(TypeNode typeNode, TypeDef nestedType) {
Debug2.Assert(nestedType is not null && nestedType.DeclaringType == typeNode.TypeDef);
typeNode.TreeNode.EnsureChildrenLoaded();
var childTypeNode = typeNode.TreeNode.DataChildren.OfType<TypeNode>().FirstOrDefault(a => a.TypeDef == nestedType);
if (childTypeNode is not null)
return childTypeNode;
childTypeNode = typeNode.Create(nestedType);
typeNode.TreeNode.AddChild(childTypeNode.TreeNode);
return childTypeNode;
}
void UpdateMemberNodes(TypeNode typeNode) {
// If it's not been loaded, we've got nothing to do
if (typeNode.TreeNode.LazyLoading)
return;
var existing = new HashSet<object?>();
foreach (var child in typeNode.TreeNode.DataChildren) {
if (child is IMDTokenNode tokenNode)
existing.Add(tokenNode.Reference);
}
foreach (var fd in typeNode.TypeDef.Fields) {
if (existing.Contains(fd))
continue;
typeNode.TreeNode.AddChild(documentTabService.DocumentTreeView.Create(fd).TreeNode);
}
foreach (var pd in typeNode.TypeDef.Properties) {
if (existing.Contains(pd))
continue;
typeNode.TreeNode.AddChild(documentTabService.DocumentTreeView.Create(pd).TreeNode);
}
foreach (var ed in typeNode.TypeDef.Events) {
if (existing.Contains(ed))
continue;
typeNode.TreeNode.AddChild(documentTabService.DocumentTreeView.Create(ed).TreeNode);
}
var accessorMethods = typeNode.TypeDef.GetPropertyAndEventMethods();
foreach (var md in typeNode.TypeDef.Methods) {
if (existing.Contains(md))
continue;
if (!accessorMethods.Contains(md))
typeNode.TreeNode.AddChild(documentTabService.DocumentTreeView.Create(md).TreeNode);
}
}
}
}