|
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Emit
{
internal abstract class DefinitionMap
{
protected readonly struct MappedMethod
{
public readonly IMethodSymbolInternal PreviousMethod;
public readonly Func<SyntaxNode, SyntaxNode?>? SyntaxMap;
public MappedMethod(IMethodSymbolInternal previousMethod, Func<SyntaxNode, SyntaxNode?>? syntaxMap)
{
PreviousMethod = previousMethod;
SyntaxMap = syntaxMap;
}
}
private readonly ImmutableDictionary<IMethodSymbolInternal, MethodInstrumentation> _methodInstrumentations;
protected readonly IReadOnlyDictionary<IMethodSymbolInternal, MappedMethod> mappedMethods;
protected abstract SymbolMatcher MapToMetadataSymbolMatcher { get; }
protected abstract SymbolMatcher MapToPreviousSymbolMatcher { get; }
protected DefinitionMap(IEnumerable<SemanticEdit> edits)
{
Debug.Assert(edits != null);
mappedMethods = GetMappedMethods(edits);
_methodInstrumentations = edits
.Where(edit => !edit.Instrumentation.IsEmpty)
.ToImmutableDictionary(edit => (IMethodSymbolInternal)GetISymbolInternalOrNull(edit.NewSymbol!)!, edit => edit.Instrumentation);
}
private IReadOnlyDictionary<IMethodSymbolInternal, MappedMethod> GetMappedMethods(IEnumerable<SemanticEdit> edits)
{
var mappedMethods = new Dictionary<IMethodSymbolInternal, MappedMethod>();
foreach (var edit in edits)
{
// We should always "preserve locals" of iterator and async methods since the state machine
// might be active without MoveNext method being on stack. We don't enforce this requirement here,
// since a method may be incorrectly marked by Iterator/AsyncStateMachine attribute by the user,
// in which case we can't reliably figure out that it's an error in semantic edit set.
// We should also "preserve locals" of any updated method containing lambdas. The goal is to
// treat lambdas the same as method declarations. Lambdas declared in a method body that escape
// the method (are assigned to a field, added to an event, e.g.) might be invoked after the method
// is updated and when it no longer contains active statements. If we didn't map the lambdas of
// the updated body to the original lambdas we would run the out-of-date lambda bodies,
// which would not happen if the lambdas were named methods.
// TODO (bug https://github.com/dotnet/roslyn/issues/2504)
// Note that in some cases an Insert might also need to map syntax. For example, a new constructor added
// to a class that has field/property initializers with lambdas. These lambdas get "copied" into the constructor
// (assuming it doesn't have "this" constructor initializer) and thus their generated names need to be preserved.
if (edit.Kind == SemanticEditKind.Update && edit.PreserveLocalVariables)
{
RoslynDebug.AssertNotNull(edit.NewSymbol);
RoslynDebug.AssertNotNull(edit.OldSymbol);
if (GetISymbolInternalOrNull(edit.NewSymbol) is IMethodSymbolInternal newMethod &&
GetISymbolInternalOrNull(edit.OldSymbol) is IMethodSymbolInternal oldMethod)
{
mappedMethods.Add(newMethod, new MappedMethod(oldMethod, edit.SyntaxMap));
}
}
}
return mappedMethods;
}
protected abstract ISymbolInternal? GetISymbolInternalOrNull(ISymbol symbol);
internal Cci.IDefinition? MapDefinition(Cci.IDefinition definition)
{
return MapToPreviousSymbolMatcher.MapDefinition(definition) ??
(MapToMetadataSymbolMatcher != MapToPreviousSymbolMatcher ? MapToMetadataSymbolMatcher.MapDefinition(definition) : null);
}
internal Cci.INamespace? MapNamespace(Cci.INamespace @namespace)
{
return MapToPreviousSymbolMatcher.MapNamespace(@namespace) ??
(MapToMetadataSymbolMatcher != MapToPreviousSymbolMatcher ? MapToMetadataSymbolMatcher.MapNamespace(@namespace) : null);
}
internal bool DefinitionExists(Cci.IDefinition definition)
=> MapDefinition(definition) is object;
internal bool NamespaceExists(Cci.INamespace @namespace)
=> MapNamespace(@namespace) is object;
internal abstract bool TryGetTypeHandle(Cci.ITypeDefinition def, out TypeDefinitionHandle handle);
internal abstract bool TryGetEventHandle(Cci.IEventDefinition def, out EventDefinitionHandle handle);
internal abstract bool TryGetFieldHandle(Cci.IFieldDefinition def, out FieldDefinitionHandle handle);
internal abstract bool TryGetMethodHandle(Cci.IMethodDefinition def, out MethodDefinitionHandle handle);
internal abstract bool TryGetPropertyHandle(Cci.IPropertyDefinition def, out PropertyDefinitionHandle handle);
internal abstract CommonMessageProvider MessageProvider { get; }
private bool TryGetMethodHandle(EmitBaseline baseline, Cci.IMethodDefinition def, out MethodDefinitionHandle handle)
{
if (this.TryGetMethodHandle(def, out handle))
{
return true;
}
var mappedDef = (Cci.IMethodDefinition?)MapToPreviousSymbolMatcher.MapDefinition(def);
if (mappedDef != null && baseline.MethodsAdded.TryGetValue(mappedDef, out int methodIndex))
{
handle = MetadataTokens.MethodDefinitionHandle(methodIndex);
return true;
}
handle = default;
return false;
}
protected static IReadOnlyDictionary<SyntaxNode, int> CreateDeclaratorToSyntaxOrdinalMap(ImmutableArray<SyntaxNode> declarators)
{
var declaratorToIndex = new Dictionary<SyntaxNode, int>();
for (int i = 0; i < declarators.Length; i++)
{
declaratorToIndex.Add(declarators[i], i);
}
return declaratorToIndex;
}
protected abstract void GetStateMachineFieldMapFromMetadata(
ITypeSymbolInternal stateMachineType,
ImmutableArray<LocalSlotDebugInfo> localSlotDebugInfo,
out IReadOnlyDictionary<EncHoistedLocalInfo, int> hoistedLocalMap,
out IReadOnlyDictionary<Cci.ITypeReference, int> awaiterMap,
out int awaiterSlotCount);
protected abstract ImmutableArray<EncLocalInfo> GetLocalSlotMapFromMetadata(StandaloneSignatureHandle handle, EditAndContinueMethodDebugInformation debugInfo);
protected abstract ITypeSymbolInternal? TryGetStateMachineType(MethodDefinitionHandle methodHandle);
internal VariableSlotAllocator? TryCreateVariableSlotAllocator(EmitBaseline baseline, Compilation compilation, IMethodSymbolInternal method, IMethodSymbolInternal topLevelMethod, DiagnosticBag diagnostics)
{
// Top-level methods are always included in the semantic edit list. Lambda methods are not.
if (!mappedMethods.TryGetValue(topLevelMethod, out var mappedMethod))
{
return null;
}
// TODO (bug https://github.com/dotnet/roslyn/issues/2504):
// Handle cases when the previous method doesn't exist.
if (!TryGetMethodHandle(baseline, (Cci.IMethodDefinition)method.GetCciAdapter(), out var previousHandle))
{
// Unrecognized method. Must have been added in the current compilation.
return null;
}
ImmutableArray<EncLocalInfo> previousLocals;
IReadOnlyDictionary<EncHoistedLocalInfo, int>? hoistedLocalMap = null;
IReadOnlyDictionary<Cci.ITypeReference, int>? awaiterMap = null;
IReadOnlyDictionary<int, KeyValuePair<DebugId, int>>? lambdaMap = null;
IReadOnlyDictionary<int, DebugId>? closureMap = null;
IReadOnlyDictionary<int, StateMachineState>? stateMachineStateMap = null;
StateMachineState? firstUnusedIncreasingStateMachineState = null;
StateMachineState? firstUnusedDecreasingStateMachineState = null;
int hoistedLocalSlotCount = 0;
int awaiterSlotCount = 0;
string? stateMachineTypeName = null;
SymbolMatcher symbolMap;
int methodIndex = MetadataTokens.GetRowNumber(previousHandle);
DebugId methodId;
// Check if method has changed previously. If so, we already have a map.
if (baseline.AddedOrChangedMethods.TryGetValue(methodIndex, out var addedOrChangedMethod))
{
methodId = addedOrChangedMethod.MethodId;
MakeLambdaAndClosureMaps(addedOrChangedMethod.LambdaDebugInfo, addedOrChangedMethod.ClosureDebugInfo, out lambdaMap, out closureMap);
MakeStateMachineStateMap(addedOrChangedMethod.StateMachineStates.States, out stateMachineStateMap);
firstUnusedIncreasingStateMachineState = addedOrChangedMethod.StateMachineStates.FirstUnusedIncreasingStateMachineState;
firstUnusedDecreasingStateMachineState = addedOrChangedMethod.StateMachineStates.FirstUnusedDecreasingStateMachineState;
if (addedOrChangedMethod.StateMachineTypeName != null)
{
// method is async/iterator kickoff method
GetStateMachineFieldMapFromPreviousCompilation(
addedOrChangedMethod.StateMachineHoistedLocalSlotsOpt,
addedOrChangedMethod.StateMachineAwaiterSlotsOpt,
out hoistedLocalMap,
out awaiterMap);
hoistedLocalSlotCount = addedOrChangedMethod.StateMachineHoistedLocalSlotsOpt.Length;
awaiterSlotCount = addedOrChangedMethod.StateMachineAwaiterSlotsOpt.Length;
// Kickoff method has no interesting locals on its own.
// We use the EnC method debug information for hoisted locals.
previousLocals = ImmutableArray<EncLocalInfo>.Empty;
stateMachineTypeName = addedOrChangedMethod.StateMachineTypeName;
}
else
{
previousLocals = addedOrChangedMethod.Locals;
}
// All types that AddedOrChangedMethodInfo refers to have been mapped to the previous generation.
// Therefore we don't need to fall back to metadata if we don't find the type reference, like we do in DefinitionMap.MapReference.
symbolMap = MapToPreviousSymbolMatcher;
}
else
{
// Method has not changed since initial generation. Generate a map
// using the local names provided with the initial metadata.
EditAndContinueMethodDebugInformation debugInfo;
StandaloneSignatureHandle localSignature;
try
{
debugInfo = baseline.DebugInformationProvider(previousHandle);
localSignature = baseline.LocalSignatureProvider(previousHandle);
}
catch (Exception e) when (e is InvalidDataException || e is IOException)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(
MessageProvider.ERR_InvalidDebugInfo,
method.Locations.First(),
method,
MetadataTokens.GetToken(previousHandle),
method.ContainingAssembly
));
return null;
}
methodId = new DebugId(debugInfo.MethodOrdinal, 0);
if (!debugInfo.Lambdas.IsDefaultOrEmpty)
{
MakeLambdaAndClosureMaps(debugInfo.Lambdas, debugInfo.Closures, out lambdaMap, out closureMap);
}
MakeStateMachineStateMap(debugInfo.StateMachineStates, out stateMachineStateMap);
if (!debugInfo.StateMachineStates.IsDefaultOrEmpty)
{
firstUnusedIncreasingStateMachineState = debugInfo.StateMachineStates.Max(s => s.StateNumber) + 1;
firstUnusedDecreasingStateMachineState = debugInfo.StateMachineStates.Min(s => s.StateNumber) - 1;
}
ITypeSymbolInternal? stateMachineType = TryGetStateMachineType(previousHandle);
if (stateMachineType != null)
{
// Method is async/iterator kickoff method.
// Use local slots stored in CDI (encLocalSlotMap) to calculate map of local variables hoisted to fields of the state machine.
var localSlotDebugInfo = debugInfo.LocalSlots.NullToEmpty();
GetStateMachineFieldMapFromMetadata(stateMachineType, localSlotDebugInfo, out hoistedLocalMap, out awaiterMap, out awaiterSlotCount);
hoistedLocalSlotCount = localSlotDebugInfo.Length;
// Kickoff method has no interesting locals on its own.
// We use the EnC method debug information for hoisted locals.
previousLocals = ImmutableArray<EncLocalInfo>.Empty;
stateMachineTypeName = stateMachineType.Name;
}
else
{
// If the current method is async/iterator then either the previous method wasn't declared as async/iterator and it's updated to be one,
// or it was but is not marked by the corresponding state machine attribute because it was missing in the compilation.
// In the later case we need to report an error since we don't known how to map to the previous state machine.
// The IDE already checked that the attribute type is present in the base compilation, but didn't validate that it is well-formed.
// We don't have the base compilation to directly query for the attribute, only the source compilation.
// But since constructor signatures can't be updated during EnC we can just check the current compilation.
if (method.IsAsync)
{
if (compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_AsyncStateMachineAttribute__ctor) == null)
{
ReportMissingStateMachineAttribute(diagnostics, method, AttributeDescription.AsyncStateMachineAttribute.FullName);
return null;
}
}
else if (method.IsIterator)
{
if (compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_IteratorStateMachineAttribute__ctor) == null)
{
ReportMissingStateMachineAttribute(diagnostics, method, AttributeDescription.IteratorStateMachineAttribute.FullName);
return null;
}
}
// Calculate local slot mapping for the current method (might be the MoveNext method of a state machine).
try
{
previousLocals = localSignature.IsNil ? ImmutableArray<EncLocalInfo>.Empty :
GetLocalSlotMapFromMetadata(localSignature, debugInfo);
}
catch (Exception e) when (e is UnsupportedSignatureContent || e is BadImageFormatException || e is IOException)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(
MessageProvider.ERR_InvalidDebugInfo,
method.Locations.First(),
method,
MetadataTokens.GetToken(localSignature),
method.ContainingAssembly
));
return null;
}
}
symbolMap = MapToMetadataSymbolMatcher;
}
return new EncVariableSlotAllocator(
symbolMap,
mappedMethod.SyntaxMap,
mappedMethod.PreviousMethod,
methodId,
previousLocals,
lambdaMap,
closureMap,
stateMachineTypeName,
hoistedLocalSlotCount,
hoistedLocalMap,
awaiterSlotCount,
awaiterMap,
stateMachineStateMap,
firstUnusedIncreasingStateMachineState,
firstUnusedDecreasingStateMachineState,
GetLambdaSyntaxFacts());
}
internal MethodInstrumentation GetMethodBodyInstrumentations(IMethodSymbolInternal method)
=> _methodInstrumentations.TryGetValue(method, out var instrumentation) ? instrumentation : MethodInstrumentation.Empty;
protected abstract LambdaSyntaxFacts GetLambdaSyntaxFacts();
private void ReportMissingStateMachineAttribute(DiagnosticBag diagnostics, IMethodSymbolInternal method, string stateMachineAttributeFullName)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(
MessageProvider.ERR_EncUpdateFailedMissingAttribute,
method.Locations.First(),
MessageProvider.GetErrorDisplayString(method.GetISymbol()),
stateMachineAttributeFullName));
}
private static void MakeLambdaAndClosureMaps(
ImmutableArray<LambdaDebugInfo> lambdaDebugInfo,
ImmutableArray<ClosureDebugInfo> closureDebugInfo,
out IReadOnlyDictionary<int, KeyValuePair<DebugId, int>> lambdaMap,
out IReadOnlyDictionary<int, DebugId> closureMap)
{
var lambdas = new Dictionary<int, KeyValuePair<DebugId, int>>(lambdaDebugInfo.Length);
var closures = new Dictionary<int, DebugId>(closureDebugInfo.Length);
for (int i = 0; i < lambdaDebugInfo.Length; i++)
{
var lambdaInfo = lambdaDebugInfo[i];
lambdas[lambdaInfo.SyntaxOffset] = KeyValuePairUtil.Create(lambdaInfo.LambdaId, lambdaInfo.ClosureOrdinal);
}
for (int i = 0; i < closureDebugInfo.Length; i++)
{
var closureInfo = closureDebugInfo[i];
closures[closureInfo.SyntaxOffset] = closureInfo.ClosureId;
}
lambdaMap = lambdas;
closureMap = closures;
}
private static void MakeStateMachineStateMap(
ImmutableArray<StateMachineStateDebugInfo> debugInfos,
out IReadOnlyDictionary<int, StateMachineState>? map)
{
map = debugInfos.IsDefault ?
null :
debugInfos.ToDictionary(entry => entry.SyntaxOffset, entry => entry.StateNumber);
}
private static void GetStateMachineFieldMapFromPreviousCompilation(
ImmutableArray<EncHoistedLocalInfo> hoistedLocalSlots,
ImmutableArray<Cci.ITypeReference?> hoistedAwaiters,
out IReadOnlyDictionary<EncHoistedLocalInfo, int> hoistedLocalMap,
out IReadOnlyDictionary<Cci.ITypeReference, int> awaiterMap)
{
var hoistedLocals = new Dictionary<EncHoistedLocalInfo, int>();
var awaiters = new Dictionary<Cci.ITypeReference, int>(Cci.SymbolEquivalentEqualityComparer.Instance);
for (int slotIndex = 0; slotIndex < hoistedLocalSlots.Length; slotIndex++)
{
var slot = hoistedLocalSlots[slotIndex];
if (slot.IsUnused)
{
// Unused field.
continue;
}
hoistedLocals.Add(slot, slotIndex);
}
for (int slotIndex = 0; slotIndex < hoistedAwaiters.Length; slotIndex++)
{
var slot = hoistedAwaiters[slotIndex];
if (slot == null)
{
// Unused awaiter.
continue;
}
awaiters.Add(slot, slotIndex);
}
hoistedLocalMap = hoistedLocals;
awaiterMap = awaiters;
}
}
}
|