Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve EnC test helpers, add repro for #67243 #67252

Merged
merged 3 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
Expand All @@ -17,15 +18,27 @@ internal sealed class GenerationInfo
public readonly MetadataReader MetadataReader;
public readonly EmitBaseline Baseline;
public readonly Action<GenerationVerifier> Verifier;

/// <summary>
/// Only available for baseline generation.
/// </summary>
public readonly CompilationVerifier? CompilationVerifier;

/// <summary>
/// Not available for baseline generation.
/// </summary>
public readonly CompilationDifference? CompilationDifference;

public GenerationInfo(CSharpCompilation compilation, MetadataReader reader, CompilationDifference? diff, EmitBaseline baseline, Action<GenerationVerifier> verifier)
public GenerationInfo(CSharpCompilation compilation, MetadataReader reader, CompilationDifference? diff, CompilationVerifier? compilationVerifier, EmitBaseline baseline, Action<GenerationVerifier> verifier)
{
Debug.Assert(diff is null ^ compilationVerifier is null);

Compilation = compilation;
MetadataReader = reader;
Baseline = baseline;
Verifier = verifier;
CompilationDifference = diff;
CompilationVerifier = compilationVerifier;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,14 @@ internal void VerifyCustomAttributes(IEnumerable<CustomAttributeRow> expected)
AssertEx.Equal(expected, _metadataReader.GetCustomAttributeRows(), itemInspector: AttributeRowToString);
}

internal void VerifyMethodBody(string qualifiedMemberName, string expectedILWithSequencePoints)
=> _generationInfo.CompilationVerifier!.VerifyMethodBody(qualifiedMemberName, expectedILWithSequencePoints);

internal void VerifyIL(string expectedIL)
{
_generationInfo.CompilationDifference!.VerifyIL(expectedIL);
}
=> _generationInfo.CompilationDifference!.VerifyIL(expectedIL);

internal void VerifyIL(string qualifiedMemberName, string expectedIL)
=> _generationInfo.CompilationDifference!.VerifyIL(qualifiedMemberName, expectedIL);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
Expand All @@ -19,64 +22,96 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests
internal sealed partial class EditAndContinueTest : IDisposable
{
private readonly CSharpCompilationOptions? _options;
private readonly CSharpParseOptions? _parseOptions;
private readonly TargetFramework _targetFramework;

private readonly List<IDisposable> _disposables = new();
private readonly List<GenerationInfo> _generations = new();
private readonly List<SourceWithMarkedNodes> _sources = new();

private bool _hasVerified;

public EditAndContinueTest(CSharpCompilationOptions? options, TargetFramework? targetFramework)
public EditAndContinueTest(CSharpCompilationOptions? options = null, CSharpParseOptions? parseOptions = null, TargetFramework targetFramework = TargetFramework.Standard)
{
_options = options;
_targetFramework = targetFramework ?? TargetFramework.Standard;
_options = options ?? TestOptions.DebugDll;
_targetFramework = targetFramework;
_parseOptions = parseOptions ?? TestOptions.Regular.WithNoRefSafetyRulesAttribute();
}

internal EditAndContinueTest AddGeneration(string source, Action<GenerationVerifier> validator)
internal EditAndContinueTest AddBaseline(string source, Action<GenerationVerifier> validator)
=> AddBaseline(EditAndContinueTestBase.MarkedSource(source, options: _parseOptions), validator);

internal EditAndContinueTest AddBaseline(SourceWithMarkedNodes source, Action<GenerationVerifier> validator)
{
_hasVerified = false;

Assert.Empty(_generations);

var compilation = CSharpTestBase.CreateCompilation(source, parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute(), options: _options, targetFramework: _targetFramework);
var compilation = CSharpTestBase.CreateCompilation(source.Tree, options: _options, targetFramework: _targetFramework);

var verifier = new CompilationVerifier(compilation);

var bytes = compilation.EmitToArray();
var md = ModuleMetadata.CreateFromImage(bytes);
verifier.Emit(
expectedOutput: null,
trimOutput: false,
expectedReturnCode: null,
args: null,
manifestResources: null,
emitOptions: EmitOptions.Default,
peVerify: Verification.Passes,
expectedSignatures: null);

var md = ModuleMetadata.CreateFromImage(verifier.EmittedAssemblyData);
_disposables.Add(md);

var baseline = EmitBaseline.CreateInitialBaseline(md, EditAndContinueTestBase.EmptyLocalsProvider);

_generations.Add(new GenerationInfo(compilation, md.MetadataReader, diff: null, baseline, validator));
_generations.Add(new GenerationInfo(compilation, md.MetadataReader, diff: null, verifier, baseline, validator));
_sources.Add(source);

return this;
}

internal EditAndContinueTest AddGeneration(string source, SemanticEditDescription[] edits, Action<GenerationVerifier> validator)
=> AddGeneration(EditAndContinueTestBase.MarkedSource(source), edits, validator);

internal EditAndContinueTest AddGeneration(SourceWithMarkedNodes source, SemanticEditDescription[] edits, Action<GenerationVerifier> validator)
{
_hasVerified = false;

Assert.NotEmpty(_generations);
Assert.NotEmpty(_sources);

var prevGeneration = _generations[^1];
var previousGeneration = _generations[^1];
var previousSource = _sources[^1];

var compilation = prevGeneration.Compilation.WithSource(source);
Assert.Equal(previousSource.MarkedSpans.IsEmpty, source.MarkedSpans.IsEmpty);

var semanticEdits = GetSemanticEdits(edits, prevGeneration.Compilation, compilation);
var compilation = previousGeneration.Compilation.WithSource(source.Tree);

var unmappedNodes = new List<SyntaxNode>();

var semanticEdits = GetSemanticEdits(edits, previousGeneration.Compilation, previousSource, compilation, source, unmappedNodes);

CompilationDifference diff;
try
{
diff = compilation.EmitDifference(prevGeneration.Baseline, semanticEdits);
diff = compilation.EmitDifference(previousGeneration.Baseline, semanticEdits);
}
catch (Exception ex)
{
throw new Exception($"Exception during generation #{_generations.Count}. See inner stack trace for details.", ex);
}

// EncVariableSlotAllocator attempted to map from current source to the previous one,
// but the mapping failed for these nodes. Mark the nodes in sources with node markers <N:x>...</N:x>.
Assert.Empty(unmappedNodes);

var md = diff.GetMetadata();
_disposables.Add(md);

_generations.Add(new GenerationInfo(compilation, md.Reader, diff, diff.NextGeneration, validator));
_generations.Add(new GenerationInfo(compilation, md.Reader, diff, compilationVerifier: null, diff.NextGeneration, validator));
_sources.Add(source);

return this;
}
Expand Down Expand Up @@ -106,9 +141,34 @@ internal EditAndContinueTest Verify()
return this;
}

private ImmutableArray<SemanticEdit> GetSemanticEdits(SemanticEditDescription[] edits, Compilation oldCompilation, Compilation newCompilation)
private ImmutableArray<SemanticEdit> GetSemanticEdits(
SemanticEditDescription[] edits,
Compilation oldCompilation,
SourceWithMarkedNodes oldSource,
Compilation newCompilation,
SourceWithMarkedNodes newSource,
List<SyntaxNode> unmappedNodes)
{
return ImmutableArray.CreateRange(edits.Select(e => new SemanticEdit(e.Kind, e.SymbolProvider(oldCompilation), e.NewSymbolProvider(newCompilation))));
var syntaxMapFromMarkers = oldSource.MarkedSpans.IsEmpty ? null : SourceWithMarkedNodes.GetSyntaxMap(oldSource, newSource, unmappedNodes);

return ImmutableArray.CreateRange(edits.Select(e =>
{
var oldSymbol = e.SymbolProvider(oldCompilation);
var newSymbol = e.NewSymbolProvider(newCompilation);

Func<SyntaxNode, SyntaxNode?>? syntaxMap;
if (e.PreserveLocalVariables)
{
Assert.Equal(SemanticEditKind.Update, e.Kind);
syntaxMap = syntaxMapFromMarkers ?? EditAndContinueTestBase.GetEquivalentNodesMap((MethodSymbol)oldSymbol, (MethodSymbol)newSymbol);
}
else
{
syntaxMap = null;
}

return new SemanticEdit(e.Kind, oldSymbol, newSymbol, syntaxMap, e.PreserveLocalVariables);
}));
}

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ internal static StatementSyntax GetNearestStatement(SyntaxNode node)
return null;
}

internal static SemanticEditDescription Edit(SemanticEditKind kind, Func<Compilation, ISymbol> symbolProvider, Func<Compilation, ISymbol> newSymbolProvider = null)
=> new(kind, symbolProvider, newSymbolProvider);
internal static SemanticEditDescription Edit(
SemanticEditKind kind,
Func<Compilation, ISymbol> symbolProvider,
Func<Compilation, ISymbol> newSymbolProvider = null,
bool preserveLocalVariables = false)
=> new(kind, symbolProvider, newSymbolProvider, preserveLocalVariables);

internal static EditAndContinueLogEntry Row(int rowNumber, TableIndex table, EditAndContinueOperation operation)
{
Expand Down
Loading