/// <summary> /// Launches the NoFuture.Tokens.Gia.InvokeAssemblyAnalysis.exe /// </summary> /// <param name="resolveGacAsmNames"></param> /// <param name="ports"> /// Optional, allows caller to specify the ports used - will use defaults /// otherwise. /// </param> /// <returns></returns> /// <remarks> /// The ports used are <see cref="DefaultPort"/> to <see cref="DefaultPort"/> + 5. /// </remarks> /// <remarks> /// <![CDATA[ /// +------------------------------------------------------------------------------------------------------------+ /// | Using AssemblyAnalysis | /// ||+--operating-context(1)--+|+-----new-instance(2)----+|+------remote-exe(3)-----+|+---target-assembly(4)---+| /// || new instance of | | | | /// || ................> | | | /// || | start new process | | | /// || | provide assembly name | | | /// || | ................> | | /// || | | launch sockets | | /// || | | get asm names on manifest| | /// || | | ................> | /// || | |send AsmIndicies on socket| | /// || | <................ | | /// || | receive AsmIndices | | | /// || | save to disk | | | /// || | assign to prop | | | /// || <................ | | | /// || invoke GetTokenIds with | | | | /// || regex | | | | /// || ................> | | | /// || | send GetTokenIdsCriteria | | | /// || | on socket | | | /// || | ................> | | /// || | | get types as tokens | | /// || | | ................> | /// || | | get members as tokens | | /// || | | ................> | /// || | | get callvirts as tokens | | /// || | | ................> | /// || | | get tokens-of-tokens(a) | | /// || | | ................> | /// || | |send TokenIds on socket(b)| | /// || | <................ | | /// || | get TokenIds | | | /// || | save to disk | | | /// || | return TokenIds | | | /// || <................ | | | /// || flatten the TokenIds | | | | /// || invoke GetTokenNames | | | | /// || ................> | | | /// || | send MetadataTokenId[] on| | | /// || | socket | | | /// || | ................> | | /// || | | resolve each to a runtime| | /// || | | type | | /// || | | ................> | /// || | | send TokenNames on socket| | /// || | <................ | | /// || | receive TokenNames | | | /// || | save to disk | | | /// || | return TokenNames | | | /// || <................ | | | /// ||+------------------------+|+------------------------+|+------------------------+|+------------------------+| /// /// (1) assume PowerShell /// (2) new instance of NoFuture.Tokens.DotNetMeta.AssemblyAnalysis /// (3) new process NoFuture.Tokens.DotNetMeta.InvokeAssemblyAnalysis.exe /// (4) the assembly whose tokens we want /// /// (a) The top-level types already had all thier members resolved to tokens and every virtcall found within the body of those members. /// This is now starting it all over again as if the types found on the callvirts in the body of these members were themselves the top-level types. /// This will continue until we end up on a type which is contained in an assembly whose name doesn't match our regex pattern. /// (b) This is a tree data-struct. Each token has itself a collection of tokens and so on. /// ]]> /// </remarks> /// <example> /// <![CDATA[ /// # will launch a console /// $myAsmAly = New-Object NoFuture.Tokens.DotNetMeta.AssemblyAnalysis($false) /// /// # this is assembly-name-to-index used to reduce the size of socket payloads /// $myAsmIndices = $myAsmAly.GetAsmIndices($AssemblyPath) /// /// # this is all the types in a similar form /// $myTokenTypes = $myAsmAly.GetTokenTypes("NoFuture.*") /// /// # this will represent the call-stack-tree in terms of just metadata token ids /// $myTokensIds = $myAsmAly.GetTokenIds(0, "NoFuture.*") /// /// # translates the metadata token ids into a token name (e.g. type, method, field, etc.) /// $myTokenNames = $myAsmAly.GetTokenNames($myTokensIds.GetAsRoot().SelectDistinct(), $true) /// ]]> /// </example> /// <example> /// <![CDATA[ /// # example using analysis results /// # say, some app with three layers: web, logic and data /// # have already run the analysis and have all the results on file as JSON /// $myAsmAly = New-Object NoFuture.Tokens.DotNetMeta.AssemblyAnalysis($false) /// /// # web layer uses types from logic layer as interfaces /// $myWebTokens = ([NoFuture.Tokens.DotNetMeta.TokenName.TokenNameResponse]::ReadFromFile($webTokensFile)).GetAsRoot() /// $myWebTypes = ([NoFuture.Tokens.DotNetMeta.TokenType.TokenTypeResponse]::ReadFromFile($webTypesFile)).GetAsRoot() /// $myWebAssemblies = [NoFuture.Tokens.DotNetMeta.TokenAsm.TAsmIndexResponse]::ReadFromFile($webAssemblyFile) /// /// #logic layer uses types from data layer, likewise, as interfaces /// $myLogicTokens = ([NoFuture.Tokens.DotNetMeta.TokenName.TokenNameResponse]::ReadFromFile($logicTokensFile)).GetAsRoot() /// $myLogicTypes = ([NoFuture.Tokens.DotNetMeta.TokenType.TokenTypeResponse]::ReadFromFile($logicTypesFile)).GetAsRoot() /// /// #data layer is as far down as we want to go /// $myDataTokens = ([NoFuture.Tokens.DotNetMeta.TokenName.TokenNameResponse]::ReadFromFile($dataTokensFile)).GetAsRoot() /// $myDataTypes = ([NoFuture.Tokens.DotNetMeta.TokenType.TokenTypeResponse]::ReadFromFile($dataTypesFile)).GetAsRoot() /// /// #expand logic layer with data layer's concrete types /// $myLogicTokens = $myAsmAly.ReassignTokenNames($myLogicTokens, $myDataTokens, $myDataTypes).GetAsRoot() /// /// #expand web layer with expanded logic layer's concrete types /// $myWebTokens = $myAsmAly.ReassignTokenNames($myWebTokens, $myLogicTokens, $myLogicTypes).GetAsRoot() /// /// #get all token names in logic layer as a Set instead of a Data-Tree /// $myFlatLogicTokens = $myLogicTokens.SelectDistinct() /// /// #do likewise for the web layer - get tokens as a Set /// $myFlatWebTokens = $myWebTokens.SelectDistinct() /// /// #now normal Set-Operations can be applied /// #say, want to find orphaned methods in logic layer no longer used by web layer /// $myOrphanedLogicTokens = $myFlatWebTokens.GetRightSetDiff($myFlatLogicTokens) /// /// #this would tell us all the types with at least one orphaned member /// $orphanedTypes = $myOrphanedLogicTokens.GetUniqueTypeNames() | Sort-Object /// /// #using NoFuture.Gen, we could remove these systematically if we know where to find the assemblies with .pdb's /// $searchDirs = @( /// "C:\Projects\MyProj\Web\bin", /// "C:\Projects\MyProj\Logic\debug\bin", /// "C:\Projects\MyProj\Data\debug\bin") /// /// /// #(in actual practice, you would want to perform null-checks, skipped here for brevity) /// $orphanedTypes | % { /// $typeName = $_ /// /// $tokenType = $myWebTypes.Items | ? {$_.Name -eq $typeName} /// /// #PDB data will not be found for interface type def's /// if($tokenType.IsInterfaceType()){ /// $tokenType = $tokenTypes.GetFirstInterfaceImplementor($tokenType) /// } /// /// #need to go backwards from a type name to some path to some assembly on the drive /// $asmPath = [NoFuture.Tokens.DotNetMeta.TokenAsm.AsmIndexResponse]::GetAssemblyPathFromRoot($typeName, $myWebAssemblies, $tokenType, $searchDirs) /// /// #now get this as a NoFuture.Gen code-gen type (assuming .NET code file was C#) /// $nfCgType = New-Object NoFuture.Gen.CgTypeCsSrcCode($asmPath,$typeName) /// /// #collect up all the orphaned members now as NoFuture.Gen.CgMember's /// $nfCgMems = New-Object "System.Collections.Generic.List[NoFuture.Gen.CgMember]" /// $orphanedMembersByType = $myOrphanedLogicTokens.SelectTypeNamesThatEndWith(([string[]]@($typeName))) /// $orphanedMembersByType | % { /// /// #NoFuture.Gen directly transforms a MetadataTokenName into a CgMember /// $nfCgMems.Add($nfCgType.CgType.FindCgMemberByTokenName($_)) /// } /// /// #blow away the orphaned members from the original source code file(s) /// [NoFuture.Gen.RefactorExtensions]::RemoveMembers($nfCgMems, $true) /// } /// ]]> /// </example> public AssemblyAnalysis(bool resolveGacAsmNames, params int[] ports) { if (string.IsNullOrWhiteSpace(NfConfig.CustomTools.InvokeAssemblyAnalysis) || !File.Exists(NfConfig.CustomTools.InvokeAssemblyAnalysis)) { throw new ItsDeadJim("Don't know where to locate the NoFuture.Tokens.DotNetMeta.InvokeAssemblyAnalysis, assign " + "the global variable at NoFuture.Shared.Cfg.NfConfig.CustomTools.InvokeAssemblyAnalysis."); } var np = DefaultPort; var usePorts = new int[6]; for (var i = 0; i < usePorts.Length; i++) { usePorts[i] = ports != null && ports.Length >= i + 1 ? ports[i] : np + i; } var args = string.Join(" ", ConsoleCmd.ConstructCmdLineArgs(GET_ASM_INDICES_PORT_CMD_SWITCH, usePorts[0].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_IDS_PORT_CMD_SWITCH, usePorts[1].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_NAMES_PORT_CMD_SWITCH, usePorts[2].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_PAGE_RANK_PORT_CMD_SWITCH, usePorts[3].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_TYPES_PORT_CMD_SWITCH, usePorts[4].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(REASSIGN_TOKEN_NAMES_PORT_CMD_SWITCH, usePorts[5].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(RESOLVE_GAC_ASM_SWITCH, resolveGacAsmNames.ToString())); MyProcess = StartRemoteProcess(NfConfig.CustomTools.InvokeAssemblyAnalysis, args); _getAsmIndiciesCmd = new InvokeGetAsmIndicies { ProcessId = MyProcess.Id, SocketPort = usePorts[0] }; _getTokenIdsCmd = new InvokeGetTokenIds { ProcessId = MyProcess.Id, SocketPort = usePorts[1] }; _getTokenNamesCmd = new InvokeGetTokenNames { ProcessId = MyProcess.Id, SocketPort = usePorts[2] }; _getTokenPageRankCmd = new InvokeGetTokenPageRank { ProcessId = MyProcess.Id, SocketPort = usePorts[3] }; _getTokenTypesCmd = new InvokeGetTokenTypes { ProcessId = MyProcess.Id, SocketPort = usePorts[4] }; _reassignTokenNamesCmd = new InvokeReassignTokenNames { ProcessId = MyProcess.Id, SocketPort = usePorts[5] }; }
/// <summary> /// Launches the NoFuture.Util.Gia.InvokeAssemblyAnalysis.exe and calls /// GetAsmIndicies command. /// </summary> /// <param name="assemblyPath"></param> /// <param name="ports"></param> /// <param name="resolveGacAsmNames"></param> /// <returns></returns> /// <remarks> /// The ports used are defaulted to <see cref="DF_START_PORT"/>. /// </remarks> /// <remarks> /// <![CDATA[ /// +------------------------------------------------------------------------------------------------------------+ /// | Using AssemblyAnalysis | /// ||+--operating-context(1)--+|+-----new-instance(2)----+|+------remote-exe(3)-----+|+---target-assembly(4)---+| /// || new instance of | | | | /// || ................> | | | /// || | start new process | | | /// || | provide assembly name | | | /// || | ................> | | /// || | | launch sockets | | /// || | | get asm names on manifest| | /// || | | ................> | /// || | |send AsmIndicies on socket| | /// || | <................ | | /// || | receive AsmIndices | | | /// || | save to disk | | | /// || | assign to prop | | | /// || <................ | | | /// || invoke GetTokenIds with | | | | /// || regex | | | | /// || ................> | | | /// || | send GetTokenIdsCriteria | | | /// || | on socket | | | /// || | ................> | | /// || | | get types as tokens | | /// || | | ................> | /// || | | get members as tokens | | /// || | | ................> | /// || | | get callvirts as tokens | | /// || | | ................> | /// || | | get tokens-of-tokens(a) | | /// || | | ................> | /// || | |send TokenIds on socket(b)| | /// || | <................ | | /// || | get TokenIds | | | /// || | save to disk | | | /// || | return TokenIds | | | /// || <................ | | | /// || flatten the TokenIds | | | | /// || invoke GetTokenNames | | | | /// || ................> | | | /// || | send MetadataTokenId[] on| | | /// || | socket | | | /// || | ................> | | /// || | | resolve each to a runtime| | /// || | | type | | /// || | | ................> | /// || | | send TokenNames on socket| | /// || | <................ | | /// || | receive TokenNames | | | /// || | save to disk | | | /// || | return TokenNames | | | /// || <................ | | | /// ||+------------------------+|+------------------------+|+------------------------+|+------------------------+| /// /// (1) assume PowerShell /// (2) new instance of NoFuture.Util.Gia.AssemblyAnalysis /// (3) new process NoFuture.Util.Gia.InvokeAssemblyAnalysis.exe /// (4) the assembly whose tokens we want /// /// (a) The top-level types already had all thier members resolved to tokens and every virtcall found within the body of those members. /// This is now starting it all over again as if the types found on the callvirts in the body of these members were themselves the top-level types. /// This will continue until we end up on a type which is contained in an assembly whose name doesn't match our regex pattern. /// (b) This is a tree data-struct. Each token has itself a collection of tokens and so on. /// ]]> /// </remarks> /// <example> /// <![CDATA[ /// $myAsmAly = New-Object NoFuture.Util.Gia.AssemblyAnalysis($AssemblyPath,$false) /// $myTokensIds = $myAsmAly.GetTokenIds(0, "NoFuture.*") /// $myFlatTokens = $myTokensIds.FlattenToDistinct() /// $myTokenNames = $myAsmAly.GetTokenNames($myFlatTokens) /// ]]> /// </example> public AssemblyAnalysis(string assemblyPath, bool resolveGacAsmNames, params int[] ports) { if (string.IsNullOrWhiteSpace(assemblyPath) || !File.Exists(assemblyPath)) throw new ItsDeadJim("This isn't a valid assembly path"); if (string.IsNullOrWhiteSpace(CustomTools.InvokeAssemblyAnalysis) || !File.Exists(CustomTools.InvokeAssemblyAnalysis)) throw new ItsDeadJim("Don't know where to locate the NoFuture.Util.Gia.InvokeAssemblyAnalysis, assign " + "the global variable at NoFuture.CustomTools.InvokeAssemblyAnalysis."); var np = DF_START_PORT; var usePorts = new int[4]; for (var i = 0; i < usePorts.Length; i++) { usePorts[i] = ports != null && ports.Length >= i + 1 ? ports[i] : np + i; } var args = string.Join(" ", ConsoleCmd.ConstructCmdLineArgs(GET_ASM_INDICES_PORT_CMD_SWITCH, usePorts[0].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_IDS_PORT_CMD_SWITCH, usePorts[1].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_NAMES_PORT_CMD_SWITCH, usePorts[2].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(GET_TOKEN_PAGE_RANK_PORT_CMD_SWITCH, usePorts[3].ToString(CultureInfo.InvariantCulture)), ConsoleCmd.ConstructCmdLineArgs(RESOLVE_GAC_ASM_SWITCH, resolveGacAsmNames.ToString())); MyProcess = StartRemoteProcess(CustomTools.InvokeAssemblyAnalysis, args); var getAsmIndicesCmd = new InvokeGetAsmIndicies() { ProcessId = MyProcess.Id, SocketPort = usePorts[0] }; _asmIndices = getAsmIndicesCmd.Receive(assemblyPath); _getTokenIdsCmd = new InvokeGetTokenIds(_asmIndices) { ProcessId = MyProcess.Id, SocketPort = usePorts[1] }; _getTokenNamesCmd = new InvokeGetTokenNames() { ProcessId = MyProcess.Id, SocketPort = usePorts[2] }; _getTokenPageRankCmd = new InvokeGetTokenPageRank { ProcessId = MyProcess.Id, SocketPort = usePorts[3] }; }