Skip to content

Commit

Permalink
Add new parser/lexer to the StackTraceAnalyzer (dotnet#57598)
Browse files Browse the repository at this point in the history
No functional changes, just moving to the new API and cleaning up unused code
  • Loading branch information
ryzngard committed Dec 1, 2021
1 parent 7577c07 commit 5b25ef9
Show file tree
Hide file tree
Showing 43 changed files with 1,281 additions and 791 deletions.
1 change: 1 addition & 0 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.UnusedReferenceAnalysis" ClassName="Microsoft.CodeAnalysis.Remote.RemoteUnusedReferenceAnalysisService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ProcessTelemetry" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProcessTelemetryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CompilationAvailable" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCompilationAvailableService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.StackTraceExplorer" ClassName="Microsoft.CodeAnalysis.Remote.Services.StackTraceExplorer.RemoteStackTraceExplorerService+Factory" />
</ItemGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.StackTraceExplorer
{
[ExportWorkspaceService(typeof(IStackTraceExplorerService)), Shared]
internal class StackTraceExplorerService : IStackTraceExplorerService
{
[ImportingConstructor]
[System.Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public StackTraceExplorerService()
{
}

public (Document? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame)
{
if (frame is ParsedStackFrame parsedFrame)
{
var matches = GetFileMatches(solution, parsedFrame.Root, out var line);
if (matches.IsEmpty)
{
return default;
}

return (matches[0], line);
}

return default;
}

public async Task<DefinitionItem?> TryFindDefinitionAsync(Solution solution, ParsedFrame frame, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken)
{
if (frame is not ParsedStackFrame parsedFrame)
{
return null;
}

var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client is not null)
{
var result = await client.TryInvokeAsync<IRemoteStackTraceExplorerService, SerializableDefinitionItem?>(
solution,
(service, solutionInfo, cancellationToken) => service.TryFindDefinitionAsync(solutionInfo, parsedFrame.ToString(), symbolPart, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (!result.HasValue)
{
return null;
}

var serializedDefinition = result.Value;
if (!serializedDefinition.HasValue)
{
return null;
}

return await serializedDefinition.Value.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
}

return await StackTraceExplorerUtilities.GetDefinitionAsync(solution, parsedFrame.Root, symbolPart, cancellationToken).ConfigureAwait(false);
}

private static ImmutableArray<Document> GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber)
{
lineNumber = 0;
if (root.FileInformationExpression is null)
{
return ImmutableArray<Document>.Empty;
}

var fileName = root.FileInformationExpression.Path.ToString();
var lineString = root.FileInformationExpression.Line.ToString();
RoslynDebug.AssertNotNull(lineString);
lineNumber = int.Parse(lineString);

var documentName = Path.GetFileName(fileName);
var potentialMatches = new HashSet<Document>();

foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
if (document.FilePath == fileName)
{
return ImmutableArray.Create(document);
}

else if (document.Name == documentName)
{
potentialMatches.Add(document);
}
}
}

return potentialMatches.ToImmutableArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.StackTraceExplorer
{
internal static class StackTraceExplorerUtilities
{
public static async Task<DefinitionItem?> GetDefinitionAsync(Solution solution, StackFrameCompilationUnit compilationUnit, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken)
{
// MemberAccessExpression is [Expression].[Identifier], and Identifier is the
// method name.
var typeExpression = compilationUnit.MethodDeclaration.MemberAccessExpression.Left;

// typeExpression.ToString() returns the full expression (or identifier)
// including arity for generic types.
var fullyQualifiedTypeName = typeExpression.ToString();

var typeName = typeExpression is StackFrameQualifiedNameNode qualifiedName
? qualifiedName.Right.ToString()
: typeExpression.ToString();

RoslynDebug.AssertNotNull(fullyQualifiedTypeName);

var methodIdentifier = compilationUnit.MethodDeclaration.MemberAccessExpression.Right;
var methodTypeArguments = compilationUnit.MethodDeclaration.TypeArguments;
var methodArguments = compilationUnit.MethodDeclaration.ArgumentList;

var methodName = methodIdentifier.ToString();

//
// Do a first pass to find projects with the type name to check first
//
using var _ = PooledObjects.ArrayBuilder<Project>.GetInstance(out var candidateProjects);
foreach (var project in solution.Projects)
{
if (!project.SupportsCompilation)
{
continue;
}

var containsSymbol = await project.ContainsSymbolsWithNameAsync(
typeName,
SymbolFilter.Type,
cancellationToken).ConfigureAwait(false);

if (containsSymbol)
{
var matchingMethods = await GetMatchingMembersFromCompilationAsync(project).ConfigureAwait(false);
if (matchingMethods.Any())
{
return await GetDefinitionAsync(matchingMethods[0]).ConfigureAwait(false);
}
}
else
{
candidateProjects.Add(project);
}
}

//
// Do a second pass to check the remaining compilations
// for the symbol, which may be a metadata symbol in the compilation
//
foreach (var project in candidateProjects)
{
var matchingMethods = await GetMatchingMembersFromCompilationAsync(project).ConfigureAwait(false);
if (matchingMethods.Any())
{
return await GetDefinitionAsync(matchingMethods[0]).ConfigureAwait(false);
}
}

return null;

//
// Local Functions
//

async Task<ImmutableArray<IMethodSymbol>> GetMatchingMembersFromCompilationAsync(Project project)
{
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var type = compilation.GetTypeByMetadataName(fullyQualifiedTypeName);
if (type is null)
{
return ImmutableArray<IMethodSymbol>.Empty;
}

var members = type.GetMembers();
return members
.OfType<IMethodSymbol>()
.Where(m => m.Name == methodName)
.Where(m => MatchTypeArguments(m.TypeArguments, methodTypeArguments))
.Where(m => MatchParameters(m.Parameters, methodArguments))
.ToImmutableArrayOrEmpty();
}

Task<DefinitionItem> GetDefinitionAsync(IMethodSymbol method)
{
ISymbol symbol = method;
if (symbolPart == StackFrameSymbolPart.ContainingType)
{
symbol = method.ContainingType;
}

return symbol.ToNonClassifiedDefinitionItemAsync(solution, includeHiddenLocations: true, cancellationToken);
}
}

private static bool MatchParameters(ImmutableArray<IParameterSymbol> parameters, StackFrameParameterList stackFrameParameters)
{
if (parameters.Length != stackFrameParameters.Parameters.Length)
{
return false;
}

for (var i = 0; i < stackFrameParameters.Parameters.Length; i++)
{
var stackFrameParameter = stackFrameParameters.Parameters[i];
var paramSymbol = parameters[i];

if (paramSymbol.Name != stackFrameParameter.Identifier.ToString())
{
return false;
}

if (!MatchType(paramSymbol.Type, stackFrameParameter.Type))
{
return false;
}
}

return true;
}

private static bool MatchTypeArguments(ImmutableArray<ITypeSymbol> typeArguments, StackFrameTypeArgumentList? stackFrameTypeArgumentList)
{
if (stackFrameTypeArgumentList is null)
{
return typeArguments.IsEmpty;
}

if (typeArguments.IsEmpty)
{
return false;
}

var stackFrameTypeArguments = stackFrameTypeArgumentList.TypeArguments;
return typeArguments.Length == stackFrameTypeArguments.Length;
}

private static bool MatchType(ITypeSymbol type, StackFrameTypeNode stackFrameType)
{
if (type is IArrayTypeSymbol arrayType)
{
if (stackFrameType is not StackFrameArrayTypeNode arrayTypeNode)
{
return false;
}

ITypeSymbol currentType = arrayType;

// Iterate through each array expression and make sure the dimensions
// match the element types in an array.
// Ex: string[,][]
// [,] is a 2 dimension array with element type string[]
// [] is a 1 dimension array with element type string
foreach (var arrayExpression in arrayTypeNode.ArrayRankSpecifiers)
{
if (currentType is not IArrayTypeSymbol currentArrayType)
{
return false;
}

if (currentArrayType.Rank != arrayExpression.CommaTokens.Length + 1)
{
return false;
}

currentType = currentArrayType.ElementType;
}

// All array types have been exchausted from the
// stackframe identifier and the type is still an array
if (currentType is IArrayTypeSymbol)
{
return false;
}

return MatchType(currentType, arrayTypeNode.TypeIdentifier);
}

return type.Name == stackFrameType.ToString();
}
}
}
Loading

0 comments on commit 5b25ef9

Please sign in to comment.