|
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.DiaSymReader;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator
{
internal sealed class EvaluationContext : EvaluationContextBase
{
private const string TypeName = "<>x";
private const string MethodName = "<>m0";
internal const bool IsLocalScopeEndInclusive = false;
internal readonly MethodContextReuseConstraints? MethodContextReuseConstraints;
internal readonly CSharpCompilation Compilation;
private readonly MethodSymbol _currentFrame;
private readonly MethodSymbol? _currentSourceMethod;
private readonly ImmutableArray<LocalSymbol> _locals;
private readonly ImmutableSortedSet<int> _inScopeHoistedLocalSlots;
private readonly MethodDebugInfo<TypeSymbol, LocalSymbol> _methodDebugInfo;
private EvaluationContext(
MethodContextReuseConstraints? methodContextReuseConstraints,
CSharpCompilation compilation,
MethodSymbol currentFrame,
MethodSymbol? currentSourceMethod,
ImmutableArray<LocalSymbol> locals,
ImmutableSortedSet<int> inScopeHoistedLocalSlots,
MethodDebugInfo<TypeSymbol, LocalSymbol> methodDebugInfo)
{
RoslynDebug.AssertNotNull(inScopeHoistedLocalSlots);
RoslynDebug.AssertNotNull(methodDebugInfo);
MethodContextReuseConstraints = methodContextReuseConstraints;
Compilation = compilation;
_currentFrame = currentFrame;
_currentSourceMethod = currentSourceMethod;
_locals = locals;
_inScopeHoistedLocalSlots = inScopeHoistedLocalSlots;
_methodDebugInfo = methodDebugInfo;
}
/// <summary>
/// Create a context for evaluating expressions at a type scope.
/// </summary>
/// <param name="compilation">Compilation.</param>
/// <param name="moduleVersionId">Module containing type</param>
/// <param name="typeToken">Type metadata token</param>
/// <returns>Evaluation context</returns>
/// <remarks>
/// No locals since locals are associated with methods, not types.
/// </remarks>
internal static EvaluationContext CreateTypeContext(
CSharpCompilation compilation,
Guid moduleVersionId,
int typeToken)
{
Debug.Assert(MetadataTokens.Handle(typeToken).Kind == HandleKind.TypeDefinition);
var currentType = compilation.GetType(moduleVersionId, typeToken);
RoslynDebug.Assert(currentType is object);
var currentFrame = new SynthesizedContextMethodSymbol(currentType);
return new EvaluationContext(
null,
compilation,
currentFrame,
currentSourceMethod: null,
locals: default,
inScopeHoistedLocalSlots: ImmutableSortedSet<int>.Empty,
methodDebugInfo: MethodDebugInfo<TypeSymbol, LocalSymbol>.None);
}
/// <summary>
/// Create a context for evaluating expressions within a method scope.
/// </summary>
/// <param name="metadataBlocks">Module metadata</param>
/// <param name="symReader"><see cref="ISymUnmanagedReader"/> for PDB associated with <paramref name="moduleVersionId"/></param>
/// <param name="moduleVersionId">Module containing method</param>
/// <param name="methodToken">Method metadata token</param>
/// <param name="methodVersion">Method version.</param>
/// <param name="ilOffset">IL offset of instruction pointer in method</param>
/// <param name="localSignatureToken">Method local signature token</param>
/// <returns>Evaluation context</returns>
internal static EvaluationContext CreateMethodContext(
ImmutableArray<MetadataBlock> metadataBlocks,
object symReader,
Guid moduleVersionId,
int methodToken,
int methodVersion,
uint ilOffset,
int localSignatureToken)
{
var offset = NormalizeILOffset(ilOffset);
var compilation = metadataBlocks.ToCompilation(moduleVersionId: default, MakeAssemblyReferencesKind.AllAssemblies);
return CreateMethodContext(
compilation,
symReader,
moduleVersionId,
methodToken,
methodVersion,
offset,
localSignatureToken);
}
/// <summary>
/// Create a context for evaluating expressions within a method scope.
/// </summary>
/// <param name="compilation">Compilation.</param>
/// <param name="symReader"><see cref="ISymUnmanagedReader"/> for PDB associated with <paramref name="moduleVersionId"/></param>
/// <param name="moduleVersionId">Module containing method</param>
/// <param name="methodToken">Method metadata token</param>
/// <param name="methodVersion">Method version.</param>
/// <param name="ilOffset">IL offset of instruction pointer in method</param>
/// <param name="localSignatureToken">Method local signature token</param>
/// <returns>Evaluation context</returns>
internal static EvaluationContext CreateMethodContext(
CSharpCompilation compilation,
object? symReader,
Guid moduleVersionId,
int methodToken,
int methodVersion,
int ilOffset,
int localSignatureToken)
{
var methodHandle = (MethodDefinitionHandle)MetadataTokens.Handle(methodToken);
var currentSourceMethod = compilation.GetSourceMethod(moduleVersionId, methodHandle);
var localSignatureHandle = (localSignatureToken != 0) ? (StandaloneSignatureHandle)MetadataTokens.Handle(localSignatureToken) : default;
var currentFrame = compilation.GetMethod(moduleVersionId, methodHandle);
RoslynDebug.AssertNotNull(currentFrame);
var symbolProvider = new CSharpEESymbolProvider(compilation.SourceAssembly, (PEModuleSymbol)currentFrame.ContainingModule, currentFrame);
var metadataDecoder = new MetadataDecoder((PEModuleSymbol)currentFrame.ContainingModule, currentFrame);
var localInfo = metadataDecoder.GetLocalInfo(localSignatureHandle);
var typedSymReader = (ISymUnmanagedReader3?)symReader;
var debugInfo = MethodDebugInfo<TypeSymbol, LocalSymbol>.ReadMethodDebugInfo(typedSymReader, symbolProvider, methodToken, methodVersion, ilOffset, isVisualBasicMethod: false);
var reuseSpan = debugInfo.ReuseSpan;
var localsBuilder = ArrayBuilder<LocalSymbol>.GetInstance();
MethodDebugInfo<TypeSymbol, LocalSymbol>.GetLocals(
localsBuilder,
symbolProvider,
debugInfo.LocalVariableNames,
localInfo,
debugInfo.DynamicLocalMap,
debugInfo.TupleLocalMap);
var inScopeHoistedLocals = debugInfo.GetInScopeHoistedLocalIndices(ilOffset, ref reuseSpan);
localsBuilder.AddRange(debugInfo.LocalConstants);
return new EvaluationContext(
new MethodContextReuseConstraints(moduleVersionId, methodToken, methodVersion, reuseSpan),
compilation,
currentFrame,
currentSourceMethod,
localsBuilder.ToImmutableAndFree(),
inScopeHoistedLocals,
debugInfo);
}
internal CompilationContext CreateCompilationContext()
{
return new CompilationContext(
Compilation,
_currentFrame,
_currentSourceMethod,
_locals,
_inScopeHoistedLocalSlots,
_methodDebugInfo);
}
/// <summary>
/// Compile a collection of expressions at the same location. If all expressions
/// compile successfully, a single assembly is returned along with the method
/// tokens for the expression evaluation methods. If there are errors compiling
/// any expression, null is returned along with the collection of error messages
/// for all expressions.
/// </summary>
/// <remarks>
/// Errors are returned as a single collection rather than grouped by expression
/// since some errors (such as those detected during emit) are not easily
/// attributed to a particular expression.
/// </remarks>
internal byte[]? CompileExpressions(
ImmutableArray<string> expressions,
out ImmutableArray<int> methodTokens,
out ImmutableArray<string> errorMessages)
{
var diagnostics = DiagnosticBag.GetInstance();
var syntaxNodes = expressions.SelectAsArray(expr => Parse(expr, treatAsExpression: true, diagnostics, out var formatSpecifiers));
byte[]? assembly = null;
if (!diagnostics.HasAnyErrors())
{
RoslynDebug.Assert(syntaxNodes.All(s => s != null));
var context = CreateCompilationContext();
if (context.TryCompileExpressions(syntaxNodes!, TypeName, MethodName, diagnostics, out var moduleBuilder))
{
using var stream = new MemoryStream();
Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBuilder, null, diagnostics, metadataOnly: false, includePrivateMembers: true),
context.MessageProvider,
() => stream,
getPortablePdbStreamOpt: null,
nativePdbWriterOpt: null,
pdbPathOpt: null,
metadataOnly: false,
isDeterministic: false,
emitTestCoverageData: false,
privateKeyOpt: null,
CancellationToken.None);
if (!diagnostics.HasAnyErrors())
{
assembly = stream.ToArray();
}
}
}
if (assembly == null)
{
methodTokens = ImmutableArray<int>.Empty;
errorMessages = ImmutableArray.CreateRange(
diagnostics.AsEnumerable().
Where(d => d.Severity == DiagnosticSeverity.Error).
Select(d => GetErrorMessage(d, CSharpDiagnosticFormatter.Instance, preferredUICulture: null)));
}
else
{
methodTokens = MetadataUtilities.GetSynthesizedMethods(assembly, MethodName);
Debug.Assert(methodTokens.Length == expressions.Length);
errorMessages = ImmutableArray<string>.Empty;
}
diagnostics.Free();
return assembly;
}
internal override CompileResult? CompileExpression(
string expr,
DkmEvaluationFlags compilationFlags,
ImmutableArray<Alias> aliases,
DiagnosticBag diagnostics,
out ResultProperties resultProperties,
CompilationTestData? testData)
{
var syntax = Parse(expr, (compilationFlags & DkmEvaluationFlags.TreatAsExpression) != 0, diagnostics, out var formatSpecifiers);
if (syntax == null)
{
resultProperties = default;
return null;
}
var context = CreateCompilationContext();
if (!context.TryCompileExpression(syntax, TypeName, MethodName, aliases, testData, diagnostics, out var moduleBuilder, out var synthesizedMethod))
{
resultProperties = default;
return null;
}
using var stream = new MemoryStream();
Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBuilder, null, diagnostics, metadataOnly: false, includePrivateMembers: true),
context.MessageProvider,
() => stream,
getPortablePdbStreamOpt: null,
nativePdbWriterOpt: null,
pdbPathOpt: null,
metadataOnly: false,
isDeterministic: false,
emitTestCoverageData: false,
privateKeyOpt: null,
CancellationToken.None);
if (diagnostics.HasAnyErrors())
{
resultProperties = default;
return null;
}
Debug.Assert(synthesizedMethod.ContainingType.MetadataName == TypeName);
Debug.Assert(synthesizedMethod.MetadataName == MethodName);
resultProperties = synthesizedMethod.ResultProperties;
return new CSharpCompileResult(
stream.ToArray(),
synthesizedMethod,
formatSpecifiers: formatSpecifiers);
}
private static CSharpSyntaxNode? Parse(
string expr,
bool treatAsExpression,
DiagnosticBag diagnostics,
out ReadOnlyCollection<string>? formatSpecifiers)
{
if (!treatAsExpression)
{
// Try to parse as a statement. If that fails, parse as an expression.
var statementDiagnostics = DiagnosticBag.GetInstance();
var statementSyntax = expr.ParseStatement(statementDiagnostics);
Debug.Assert((statementSyntax == null) || !statementDiagnostics.HasAnyErrors());
statementDiagnostics.Free();
var isExpressionStatement = statementSyntax.IsKind(SyntaxKind.ExpressionStatement);
if (statementSyntax != null && !isExpressionStatement)
{
formatSpecifiers = null;
if (statementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement))
{
return statementSyntax;
}
diagnostics.Add(ErrorCode.ERR_ExpressionOrDeclarationExpected, Location.None);
return null;
}
}
return expr.ParseExpression(diagnostics, allowFormatSpecifiers: true, out formatSpecifiers);
}
internal override CompileResult? CompileAssignment(
string target,
string expr,
ImmutableArray<Alias> aliases,
DiagnosticBag diagnostics,
out ResultProperties resultProperties,
CompilationTestData? testData)
{
var assignment = target.ParseAssignment(expr, diagnostics);
if (assignment == null)
{
resultProperties = default;
return null;
}
var context = CreateCompilationContext();
if (!context.TryCompileAssignment(assignment, TypeName, MethodName, aliases, testData, diagnostics, out var moduleBuilder, out var synthesizedMethod))
{
resultProperties = default;
return null;
}
using var stream = new MemoryStream();
Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBuilder, null, diagnostics, metadataOnly: false, includePrivateMembers: true),
context.MessageProvider,
() => stream,
getPortablePdbStreamOpt: null,
nativePdbWriterOpt: null,
pdbPathOpt: null,
metadataOnly: false,
isDeterministic: false,
emitTestCoverageData: false,
privateKeyOpt: null,
CancellationToken.None);
if (diagnostics.HasAnyErrors())
{
resultProperties = default;
return null;
}
Debug.Assert(synthesizedMethod.ContainingType.MetadataName == TypeName);
Debug.Assert(synthesizedMethod.MetadataName == MethodName);
resultProperties = synthesizedMethod.ResultProperties;
return new CSharpCompileResult(
stream.ToArray(),
synthesizedMethod,
formatSpecifiers: null);
}
private static readonly ReadOnlyCollection<byte> s_emptyBytes =
new ReadOnlyCollection<byte>(Array.Empty<byte>());
internal override ReadOnlyCollection<byte> CompileGetLocals(
ArrayBuilder<LocalAndMethod> locals,
bool argumentsOnly,
ImmutableArray<Alias> aliases,
DiagnosticBag diagnostics,
out string typeName,
CompilationTestData? testData)
{
var context = CreateCompilationContext();
var moduleBuilder = context.CompileGetLocals(TypeName, locals, argumentsOnly, aliases, testData, diagnostics);
ReadOnlyCollection<byte>? assembly = null;
if (moduleBuilder != null && locals.Count > 0)
{
using var stream = new MemoryStream();
Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBuilder, null, diagnostics, metadataOnly: false, includePrivateMembers: true),
context.MessageProvider,
() => stream,
getPortablePdbStreamOpt: null,
nativePdbWriterOpt: null,
pdbPathOpt: null,
metadataOnly: false,
isDeterministic: false,
emitTestCoverageData: false,
privateKeyOpt: null,
CancellationToken.None);
if (!diagnostics.HasAnyErrors())
{
assembly = new ReadOnlyCollection<byte>(stream.ToArray());
}
}
if (assembly == null)
{
locals.Clear();
assembly = s_emptyBytes;
}
typeName = TypeName;
return assembly;
}
internal override bool HasDuplicateTypesOrAssemblies(Diagnostic diagnostic)
{
switch ((ErrorCode)diagnostic.Code)
{
case ErrorCode.ERR_DuplicateImport:
case ErrorCode.ERR_DuplicateImportSimple:
case ErrorCode.ERR_SameFullNameAggAgg:
case ErrorCode.ERR_AmbigCall:
return true;
default:
return false;
}
}
internal override ImmutableArray<AssemblyIdentity> GetMissingAssemblyIdentities(Diagnostic diagnostic, AssemblyIdentity linqLibrary)
{
return GetMissingAssemblyIdentitiesHelper((ErrorCode)diagnostic.Code, diagnostic.Arguments, linqLibrary);
}
/// <remarks>
/// Internal for testing.
/// </remarks>
internal static ImmutableArray<AssemblyIdentity> GetMissingAssemblyIdentitiesHelper(ErrorCode code, IReadOnlyList<object?> arguments, AssemblyIdentity linqLibrary)
{
RoslynDebug.AssertNotNull(linqLibrary);
switch (code)
{
case ErrorCode.ERR_NoTypeDef:
case ErrorCode.ERR_GlobalSingleTypeNameNotFoundFwd:
case ErrorCode.ERR_DottedTypeNameNotFoundInNSFwd:
case ErrorCode.ERR_SingleTypeNameNotFoundFwd:
case ErrorCode.ERR_NameNotInContextPossibleMissingReference: // Probably can't happen.
foreach (var argument in arguments)
{
var identity = (argument as AssemblyIdentity) ?? (argument as AssemblySymbol)?.Identity;
if (identity != null && !identity.Equals(MissingCorLibrarySymbol.Instance.Identity))
{
return ImmutableArray.Create(identity);
}
}
break;
case ErrorCode.ERR_DottedTypeNameNotFoundInNS:
if (arguments.Count == 2 &&
arguments[0] is string namespaceName &&
arguments[1] is NamespaceSymbol containingNamespace &&
containingNamespace.ConstituentNamespaces.Any(static n => n.ContainingAssembly.Identity.IsWindowsAssemblyIdentity()))
{
// This is just a heuristic, but it has the advantage of being portable, particularly
// across different versions of (desktop) windows.
var identity = new AssemblyIdentity($"{containingNamespace.ToDisplayString()}.{namespaceName}", contentType: System.Reflection.AssemblyContentType.WindowsRuntime);
return ImmutableArray.Create(identity);
}
break;
case ErrorCode.ERR_NoSuchMemberOrExtension: // Commonly, but not always, caused by absence of System.Core.
case ErrorCode.ERR_DynamicAttributeMissing:
case ErrorCode.ERR_DynamicRequiredTypesMissing:
// MSDN says these might come from System.Dynamic.Runtime
case ErrorCode.ERR_QueryNoProviderStandard:
case ErrorCode.ERR_ExtensionAttrNotFound: // Probably can't happen.
return ImmutableArray.Create(linqLibrary);
case ErrorCode.ERR_BadAwaitArg_NeedSystem:
Debug.Assert(false, "Roslyn no longer produces ERR_BadAwaitArg_NeedSystem");
break;
}
return default;
}
}
}
|