Skip to content

Fix #line directive in rebuild scenarios#52960

Merged
jaredpar merged 17 commits intodotnet:release/dev16.11from
jaredpar:line
May 6, 2021
Merged

Fix #line directive in rebuild scenarios#52960
jaredpar merged 17 commits intodotnet:release/dev16.11from
jaredpar:line

Conversation

@jaredpar
Copy link
Copy Markdown
Member

This adds proper support for handling #line and #ExternalSource
directives during rebuild scenarios. There are actually two
different problems that need to be solved here.

The first is handling of the paths in the NormalizePath operation.
The names used in these directives can be relative paths. That means
during initial compilation they are normalized in the context of the
containing file name. That always succeeds because in initial
compilation the containing file name is a legal file path.

During rebuild though the containing file name may not be a legal
path; it could be running a rebuild where /pathmap was used to create
non-legal paths on the current operating system. Hence if nothing is
done then normalization will return the literal name in the source file.
That causes conflicts because the same name can be used in different
files in different directories. Hence we need to provide a
SourceReferenceResolver that gives unique paths for every #line
pairing.

The second is that we need to add back in the non-source file documents
into the PDB after the source files are written. That requires extra
data being passed down to the emit phase

This adds proper support for handling `#line` and `#ExternalSource`
directives during rebuild scenarios.  There are actually two
different problems that need to be solved here.

The first is handling of the paths in the `NormalizePath` operation.
The names used in these directives can be relative paths. That means
during initial compilation they are normalized in the context of the
containing file name. That always succeeds because in initial
compilation the containing file name is a legal file path.

During rebuild though the containing file name may not be a legal
path; it could be running a rebuild where `/pathmap` was used to create
non-legal paths on the current operating system. Hence if nothing is
done then normalization will return the literal name in the source file.
That causes conflicts because the same name can be used in different
files in different directories. Hence we need to provide a
`SourceReferenceResolver` that gives unique paths for every `#line`
pairing.

The second is that we need to add back in the non-source file documents
into the PDB after the source files are written. That requires extra
data being passed down to the emit phase
@jaredpar jaredpar marked this pull request as ready for review April 27, 2021 16:10
@jaredpar jaredpar requested a review from a team as a code owner April 27, 2021 16:10
@jaredpar
Copy link
Copy Markdown
Member Author

@dotnet/roslyn-compiler PTAL

Comment thread src/Compilers/Core/Portable/Emit/Context.cs Outdated
@RikkiGibson RikkiGibson self-assigned this Apr 27, 2021
Comment thread src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs Outdated

var compilationState = new TypeCompilationState(null, _compilation, _moduleBeingBuiltOpt);
foreach (Cci.IMethodDefinition definition in privateImplClass.GetMethods(new EmitContext(_moduleBeingBuiltOpt, null, diagnostics.DiagnosticBag, metadataOnly: false, includePrivateMembers: true)))
foreach (Cci.IMethodDefinition definition in privateImplClass.GetMethods(new EmitContext(_moduleBeingBuiltOpt, null, null, diagnostics.DiagnosticBag, metadataOnly: false, includePrivateMembers: true)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice to label these null literals the same way we label the bool literals

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I wanted to make sure we all agreed that EmitContext was the right way to flow data here (put acomment in the PR to get clarity on that). If we all agree I'll go back and clean this up. Didn't want to do the work if we were going to decide to thread it through separately cause then I'd have to also undo it.

Comment thread src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs Outdated
Comment thread src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs Outdated
Comment thread src/Compilers/Core/Portable/Compilation/RebuildData.cs Outdated
Comment thread src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs Outdated
Comment thread src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs Outdated
Comment thread src/Compilers/Core/Portable/Compilation/RebuildData.cs Outdated
@jaredpar
Copy link
Copy Markdown
Member Author

The non-windows test failures look real. I'm digging into them. Strangely they are VB only.

@tmat
Copy link
Copy Markdown
Member

tmat commented Apr 28, 2021

Will take a look next week.

Comment thread src/Compilers/Core/Portable/Compilation/RebuildData.cs Outdated
@jaredpar
Copy link
Copy Markdown
Member Author

Sorry I keep pushing commits that say "fix tests" only to have the tests break again. I'm in a situation where the tests pass on one OS but fail on the other. So I fix everything on Windows, push, see Linux break, fix on Linux, push, watch Windows break, rinse repeat. It's a bit frustrating but think I'm at the bottom now.

var name = document.Location;
if (_sourceDebugDocumentsAdded && this.Context.RebuildDataOpt is { } rebuildData)
{
name = rebuildData.GetNextNonSourceFileDocumentName();
Copy link
Copy Markdown
Contributor

@cston cston Apr 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name = rebuildData.GetNextNonSourceFileDocumentName()

It looks fragile to rely on the order that AddDocument() is called. Could we simply add all NonSourceFileDocumentNames directly at the point in MetadataWriter.BuildMetadataAndIL() where we set _sourceDebugDocumentsAdded? #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately no we can't.

Have to get into how #line are processed a bit to understand the problem here. The #line directives are processed initially in GenerateMethodBody and that runs in parallel across the compilation. As they are processed there we essentially have to do two operations:

  • Generate a path via NormalizePath. The invariant here is that for two calls to NormalizePath for the same logical file name in a #line directive we need to return the same string. By "logical name" I mean that two files in the same directive with a #line 42 "data.txt" need to return the same name but a file in a different directory with the same #line 42 "data.txt" needs to return a different name.
  • Generate a DebugSourceDocument for that path. As long as we maintain the invariant above this will just work out.

This gets us up to the point where we set _sourceDebugDocumentsAdded to true. At this point we have:

  1. The dictionary of string (the path) to the DebugSourceDocument entries
  2. The set of non-source file names we need to emit into the PDB

While we absolutely know the order in which (2) needs to be emitted we don't know the order of (1) and that's important. The final stage of emit is going to lookup DebugSourceDocument in the same order as the original compilation via AddDocument and we have to make sure all the identities match here (particularly the numeric token attached with the names, documents, etc ...) It's also not possible to map (2) back to (1) without a decent investment in how we deal with full paths.

In chatting with @tmat we favored just hooking AddDocument since because of determinism we understood that call will come in a predictable order.

I think with enough work I could make it possible to do the name mapping correctly in NormalizePath but I worry I'm going to miss alot of casese. Particularly if users invest in very non-standard pathing conventions in /pathmap.

Very open to suggestions here if you see a better way.

Comment thread src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs Outdated
return @$"{root}\{path}";
}

return null;
Copy link
Copy Markdown
Contributor

@cston cston Apr 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return null;

Should this be return @$"{baseFilePath}\{path}";?

And if so, consider just updating baseFilePath inside the if above, so there's only one return that concatenates. #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be return @$"{baseFilePath}\{path}"; because we need #line directives in different files in the same directory to normalize to the same final path. If we took this suggestion then we would end up with two documents instead of one.

Comment thread src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs
Comment thread src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs Outdated
Comment thread src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs Outdated
Comment thread src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs Outdated
@jaredpar
Copy link
Copy Markdown
Member Author

jaredpar commented May 3, 2021

Responded to PR feedback

RebuildData? rebuildDataOpt = null,
DiagnosticBag? diagnostics = null,
bool metadataOnly = false,
bool includePrivateMembers = true)
Copy link
Copy Markdown
Contributor

@cston cston May 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we relying on any of these default values currently? If not, consider removing the default values since it feels like callers should be explicit about some of these values, particularly diagnostics which should perhaps be non-nullable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took this approach to minimize the diff for now. Wanted to wait on @tmat to give feedback on whether this was an appropriate way to pass around RebuildData. If he confirms I'll remove all the default values here and clean up the callers.

@RikkiGibson RikkiGibson self-requested a review May 3, 2021 17:46
@RikkiGibson
Copy link
Copy Markdown
Member

It looks like there is a formatting violation and some test failures. PDBTests.InvalidCharacterInPdbPath notably seems to be failing on Windows.

Comment thread src/Compilers/Core/Portable/Compilation/RebuildData.cs
Copy link
Copy Markdown
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Comment thread src/Compilers/Core/Rebuild/CompilationFactory.cs Outdated
{
get
{
// WSL environments can have this value as empty string
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

were you missing some english-only tests on linux locally?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Several of the VB rebuild tests were english only and I couldn't run them on my WSL environment but they were failing in CI. This unblocked me.

public bool IsRefAssembly => MetadataOnly && !IncludePrivateMembers;

public EmitContext(CommonPEModuleBuilder module, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers)
public EmitContext(CommonPEModuleBuilder module, SyntaxNode? syntaxNodeOpt, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public EmitContext(CommonPEModuleBuilder module, SyntaxNode? syntaxNodeOpt, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers)
public EmitContext(CommonPEModuleBuilder module, SyntaxNode? syntaxNodeOpt, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers)

@jaredpar jaredpar enabled auto-merge (squash) May 5, 2021 18:01
Comment thread src/Compilers/Core/Portable/Compilation/RebuildData.cs Outdated
Co-authored-by: Rikki Gibson <rikkigibson@gmail.com>
@jaredpar jaredpar changed the base branch from main to release/dev16.11 May 6, 2021 20:07
@jaredpar
Copy link
Copy Markdown
Member Author

jaredpar commented May 6, 2021

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@jaredpar jaredpar merged commit c42df4b into dotnet:release/dev16.11 May 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants