context: /*
      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 System.Text;
  using System.Windows.Threading;
  using dnlib.DotNet;
  using dnlib.DotNet.Emit;
  using dnlib.DotNet.Resources;
  using dnSpy.Contracts.Decompiler;
  using dnSpy.Contracts.Documents;
  using dnSpy.Contracts.Documents.TreeView;
  using dnSpy.Contracts.Documents.TreeView.Resources;
  using dnSpy.Contracts.Images;
  using dnSpy.Contracts.Search;
  
  namespace dnSpy.Search {
  	/// <summary>
  	/// Searches types/members/etc for text. A filter decides which type/member/etc to check.
  	/// </summary>
  	sealed class FilterSearcher {
  		readonly FilterSearcherOptions options;
  
  		public FilterSearcher(FilterSearcherOptions options) => this.options = options;
  
  		bool IsMatch(string? text, object? obj) => options.SearchComparer.IsMatch(text, obj);
  
  		public void SearchAssemblies(IEnumerable<DsDocumentNode> fileNodes) {
  			foreach (var fileNode in fileNodes) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				if (fileNode is AssemblyDocumentNode)
  					SearchAssemblyInternal((AssemblyDocumentNode)fileNode);
  				else if (fileNode is ModuleDocumentNode)
  					SearchModule(fileNode.Document);
  			}
  		}
  
  		public void SearchTypes(IEnumerable<SearchTypeInfo> types) {
  			foreach (var info in types) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				if (info.Type.DeclaringType is null)
  					Search(info.Document, info.Type.Namespace, info.Type);
  				else
  					Search(info.Document, info.Type);
  			}
  		}
  
  		void CheckCustomAttributes(IDsDocument file, IHasCustomAttribute hca, object? parent) {
  			var res = options.Filter.GetResultAttributes(hca);
  			if (!res.IsMatch)
  				return;
  			foreach (var ca in hca.GetCustomAttributes()) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				foreach (var o in ca.ConstructorArguments) {
  					options.CancellationToken.ThrowIfCancellationRequested();
  					if (CheckCA(file, hca, parent, o))
  						return;
  				}
  				foreach (var o in ca.NamedArguments) {
  					options.CancellationToken.ThrowIfCancellationRequested();
  					if (CheckCA(file, hca, parent, o.Argument))
  						return;
  				}
  			}
  		}
  
  		bool CheckCA(IDsDocument file, IHasCustomAttribute hca, object? parent, CAArgument o) {
  			var value = o.Value;
  			var u = value as UTF8String;
  			if (!(u is null))
  				value = u.String;
  			if (!IsMatch(null, value))
  				return false;
  			options.OnMatch(new SearchResult {
  				Context = options.Context,
  				Object = hca,
  				NameObject = hca,
  				ObjectImageReference = GetImageReference(hca),
  				LocationObject = parent is string s ? new NamespaceSearchResult(s) : parent,
  				LocationImageReference = GetImageReference(parent),
  				Document = file,
  			});
  			return true;
  		}
  
  		ImageReference GetImageReference(object? obj) {
  			if (obj is ModuleDef)
  				return options.DotNetImageService.GetImageReference((ModuleDef)obj);
  			if (obj is AssemblyDef)
  				return options.DotNetImageService.GetImageReference((AssemblyDef)obj);
  			if (obj is TypeDef)
  				return options.DotNetImageService.GetImageReference((TypeDef)obj);
  			if (obj is MethodDef)
  				return options.DotNetImageService.GetImageReference((MethodDef)obj);
  			if (obj is FieldDef)
  				return options.DotNetImageService.GetImageReference((FieldDef)obj);
  			if (obj is PropertyDef)
  				return options.DotNetImageService.GetImageReference((PropertyDef)obj);
  			if (obj is EventDef)
  				return options.DotNetImageService.GetImageReference((EventDef)obj);
  			if (obj is ParamDef)
  				return options.DotNetImageService.GetImageReferenceParameter();
  			if (obj is GenericParam)
  				return options.DotNetImageService.GetImageReferenceGenericParameter();
  			if (obj is string)
  				return options.DotNetImageService.GetNamespaceImageReference();
  
  			return new ImageReference();
  		}
  
  		void SearchAssemblyInternal(AssemblyDocumentNode asmNode) {
  			if (asmNode is null)
  				return;
  			var asm = asmNode.Document.AssemblyDef;
  			Debug.Assert(!(asm is null));
  			if (asm is null)
  				return;
  			var res = options.Filter.GetResult(asm);
  			if (res.FilterType == FilterType.Hide)
  				return;
  			CheckCustomAttributes(asmNode.Document, asm, null);
  
  			if (res.IsMatch && (IsMatch(asm.FullName, asmNode.Document) || IsMatch(asm.Name, null))) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = asm,
  					NameObject = asm,
  					ObjectImageReference = options.DotNetImageService.GetImageReference(asmNode.Document.ModuleDef!),
  					LocationObject = null,
  					LocationImageReference = new ImageReference(),
  					Document = asmNode.Document,
  				});
  			}
  
  			if (asmNode.TreeNode.LazyLoading) {
  				options.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => {
  					asmNode.TreeNode.EnsureChildrenLoaded();
  				}));
  			}
  			var modChildren = asmNode.TreeNode.DataChildren.OfType<ModuleDocumentNode>().ToArray();
  
  			foreach (var node in asmNode.TreeNode.DataChildren) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				if (node is ModuleDocumentNode modNode)
  					SearchModule(modNode.Document);
  			}
  		}
  
  		void SearchModule(IDsDocument module) {
  			if (module is null)
  				return;
  			var mod = module.ModuleDef;
  			if (mod is null) {
  				SearchNonNetFile(module);
  				return;
  			}
  
  			var res = options.Filter.GetResult(mod);
  			if (res.FilterType == FilterType.Hide)
  				return;
  			CheckCustomAttributes(module, mod, mod.Assembly);
  
  			if (res.IsMatch && IsMatch(mod.FullName, module)) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = mod,
  					NameObject = mod,
  					ObjectImageReference = options.DotNetImageService.GetImageReference(mod),
  					LocationObject = mod.Assembly,
  					LocationImageReference = !(mod.Assembly is null) ? options.DotNetImageService.GetImageReference(mod.Assembly.ManifestModule) : new ImageReference(),
  					Document = module,
  				});
  			}
  
  			SearchModAsmReferences(module);
  			SearchResources(module);
  
  			foreach (var kv in GetNamespaces(mod)) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				Search(module, kv.Key, kv.Value);
  			}
  		}
  
  		void SearchModAsmReferences(IDsDocument module) {
  			var res = options.Filter.GetResult((ReferencesFolderNode?)null);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			foreach (var asmRef in module.ModuleDef!.GetAssemblyRefs()) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				res = options.Filter.GetResult(asmRef);
  				if (res.FilterType == FilterType.Hide)
  					continue;
  
  				if (res.IsMatch && (IsMatch(asmRef.FullName, asmRef) || IsMatch(asmRef.Name, null))) {
  					options.OnMatch(new SearchResult {
  						Context = options.Context,
  						Object = asmRef,
  						NameObject = asmRef,
  						ObjectImageReference = options.DotNetImageService.GetImageReferenceAssemblyRef(),
  						LocationObject = module.ModuleDef,
  						LocationImageReference = options.DotNetImageService.GetImageReference(module.ModuleDef),
  						Document = module,
  					});
  				}
  			}
  
  			foreach (var modRef in module.ModuleDef.GetModuleRefs()) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				res = options.Filter.GetResult(modRef);
  				if (res.FilterType == FilterType.Hide)
  					continue;
  
  				if (res.IsMatch && IsMatch(modRef.FullName, modRef)) {
  					options.OnMatch(new SearchResult {
  						Context = options.Context,
  						Object = modRef,
  						NameObject = modRef,
  						ObjectImageReference = options.DotNetImageService.GetImageReferenceModuleRef(),
  						LocationObject = module.ModuleDef,
  						LocationImageReference = options.DotNetImageService.GetImageReference(module.ModuleDef),
  						Document = module,
  					});
  				}
  			}
  		}
  
  		void SearchResources(IDsDocument module) {
  			var res = options.Filter.GetResult((ResourcesFolderNode?)null);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			res = options.Filter.GetResult((ResourceNode?)null);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			var resNodes = new List<ResourceNode>();
  			options.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => {
  				var modNode = options.DocumentTreeView.FindNode(module.ModuleDef);
  				if (modNode is null)
  					return;
  				modNode.TreeNode.EnsureChildrenLoaded();
  				var resFolder = modNode.TreeNode.Children.FirstOrDefault(a => a.Data is ResourcesFolderNode);
  				if (!(resFolder is null)) {
  					resFolder.EnsureChildrenLoaded();
  					resNodes.AddRange(resFolder.DataChildren.OfType<ResourceNode>());
  				}
  			}));
  
  			foreach (var node in resNodes) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				SearchResourceTreeNodes(module, node);
  			}
  		}
  
  		string? ToString(IResourceNode resource) {
  			try {
  				return resource.ToString(options.CancellationToken, options.SearchDecompiledData);
  			}
  			catch (OperationCanceledException) {
  				throw;
  			}
  			catch {
  			}
  			return string.Empty;
  		}
  
  		void SearchResourceTreeNodes(IDsDocument module, ResourceNode resTreeNode) {
  			var res = options.Filter.GetResult(resTreeNode);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			if (res.IsMatch && (IsMatch(resTreeNode.Name, resTreeNode) || IsMatch(ToString(resTreeNode), null))) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = resTreeNode,
  					NameObject = resTreeNode,
  					ObjectImageReference = resTreeNode.Icon,
  					LocationObject = module.ModuleDef,
  					LocationImageReference = options.DotNetImageService.GetImageReference(module.ModuleDef!),
  					Document = module,
  				});
  			}
  
  			res = options.Filter.GetResult((ResourceElementNode?)null);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			var resNodes = new List<ResourceElementNode>();
  			options.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => {
  				resTreeNode.TreeNode.EnsureChildrenLoaded();
  				resNodes.AddRange(resTreeNode.TreeNode.DataChildren.OfType<ResourceElementNode>());
  			}));
  
  			foreach (var resElNode in resNodes) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				SearchResourceElementTreeNode(module, resTreeNode, resElNode);
  			}
  		}
  
  		void SearchResourceElementTreeNode(IDsDocument module, ResourceNode resTreeNode, ResourceElementNode resElNode) {
  			var res = options.Filter.GetResult(resElNode);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			if (res.IsMatch) {
  				bool m = IsMatch(resElNode.Name, resElNode) || IsMatch(Uri.UnescapeDataString(resElNode.Name), resElNode);
  				if (!m) {
  					if (resElNode.ResourceElement.ResourceData is BuiltInResourceData builtin) {
  						var val = builtin.Data;
  						if (builtin.Code == ResourceTypeCode.TimeSpan)
  							val = ((TimeSpan)val).Ticks;
  						m = IsMatch(val as string, val);
  					}
  				}
  				if (!m)
  					m = IsMatch(ToString(resElNode), null);
  				if (m) {
  					options.OnMatch(new SearchResult {
  						Context = options.Context,
  						Object = resElNode,
  						NameObject = resElNode,
  						ObjectImageReference = resElNode.Icon,
  						LocationObject = resTreeNode,
  						LocationImageReference = resTreeNode.Icon,
  						Document = module,
  					});
  				}
  			}
  		}
  
  		Dictionary<string, List<TypeDef>> GetNamespaces(ModuleDef module) {
  			var ns = new Dictionary<string, List<TypeDef>>(StringComparer.Ordinal);
  
  			foreach (var type in module.Types) {
  				if (!ns.TryGetValue(type.Namespace, out var list))
  					ns.Add(type.Namespace, list = new List<TypeDef>());
  				list.Add(type);
  			}
  
  			return ns;
  		}
  
  		void SearchNonNetFile(IDsDocument nonNetFile) {
  			if (nonNetFile is null)
  				return;
  			var res = options.Filter.GetResult(nonNetFile);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			if (res.IsMatch && IsMatch(nonNetFile.GetShortName(), nonNetFile)) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = nonNetFile,
  					NameObject = nonNetFile,
  					ObjectImageReference = options.DotNetImageService.GetImageReference(nonNetFile.PEImage!),
  					LocationObject = null,
  					LocationImageReference = new ImageReference(),
  					Document = nonNetFile,
  				});
  			}
  		}
  
  		void Search(IDsDocument ownerModule, string ns, List<TypeDef> types) {
  			var res = options.Filter.GetResult(ns, ownerModule);
  			if (res.FilterType == FilterType.Hide)
  				return;
  
  			if (res.IsMatch && IsMatch(ns, ns)) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = ns,
  					NameObject = new NamespaceSearchResult(ns),
  					ObjectImageReference = options.DotNetImageService.GetNamespaceImageReference(),
  					LocationObject = ownerModule.ModuleDef,
  					LocationImageReference = options.DotNetImageService.GetImageReference(ownerModule.ModuleDef!),
  					Document = ownerModule,
  				});
  			}
  
  			foreach (var type in types) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				Search(ownerModule, ns, type);
  			}
  		}
  
  		void Search(IDsDocument ownerModule, string nsOwner, TypeDef type) {
  			var res = options.Filter.GetResult(type);
  			if (res.FilterType == FilterType.Hide)
  				return;
  			CheckCustomAttributes(ownerModule, type, nsOwner);
  
  			if (res.IsMatch && (IsMatch(FixTypeName(type.FullName), type) || IsMatch(FixTypeName(type.Name), type))) {
  				options.OnMatch(new SearchResult {
  					Context = options.Context,
  					Object = type,
  					NameObject = type,
  					ObjectImageReference = options.DotNetImageService.GetImageReference(type),
  					LocationObject = new NamespaceSearchResult(nsOwner),
  					LocationImageReference = options.DotNetImageService.GetNamespaceImageReference(),
  					Document = ownerModule,
  				});
  			}
  
  			SearchMembers(ownerModule, type);
  
  			foreach (var subType in type.GetTypes()) {
  				options.CancellationToken.ThrowIfCancellationRequested();
  				Search(ownerModule, subType);
  			}
  		}
  
  		static string FixTypeName(string name) {
  			int i;
  			for (i = 0; i < name.Length; i++) {
  				var c = name[i];
  				if (c == '/' || c == '`')
  					break;
  			}
  			if (i == name.Length)
  				return name;
  			var sb = new StringBuilder();
  			sb.Append(name, 0, i);
  			for (; i < name.Length; i++) {
  				var c = name[i];
  
gt: 				switch (c) {