Skip to content

Annotate CommandLineParser/Arguments and File/PathUtilities#41063

Merged
jcouv merged 21 commits intodotnet:masterfrom
jcouv:annotate-cmd
Feb 15, 2020
Merged

Annotate CommandLineParser/Arguments and File/PathUtilities#41063
jcouv merged 21 commits intodotnet:masterfrom
jcouv:annotate-cmd

Conversation

@jcouv
Copy link
Copy Markdown
Member

@jcouv jcouv commented Jan 17, 2020

This found a bug (added tests and a simple fix in CommandLineParser.EnumerateFiles).
I also added a null check in CommandLineParser (line 428) which I could not protect or rationalize otherwise.

@jcouv jcouv added Area-Compilers Concept-Null Annotations The issue involves annotating an API for nullable reference types labels Jan 17, 2020
@jcouv jcouv self-assigned this Jan 17, 2020
/// Gets the emit options.
/// </summary>
public EmitOptions EmitOptions { get; internal set; }
public EmitOptions EmitOptions { get; internal set; } = null!; // initialized by Parse
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 18, 2020

Choose a reason for hiding this comment

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

❓ Why not make this a constructor parameter (required initialization)? #Resolved

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.

Tried it after your suggestion, but decided against it as it breaks the regularity of the code in parser and also implies a change in VB code. Neither of these seem worth it.
The topic of field initializers will be re-discussed in LDM and at the very least we'll provide some guidance to the recommended pattern for dealing with this situation.


In reply to: 368190989 [](ancestors = 368190989)


internal static ReportDiagnostic GetDiagnosticOptionsFromRulesetFile(string rulesetFileFullPath, out Dictionary<string, ReportDiagnostic> diagnosticOptions, IList<Diagnostic> diagnosticsOpt, CommonMessageProvider messageProviderOpt)
#nullable enable
internal static ReportDiagnostic GetDiagnosticOptionsFromRulesetFile(string? rulesetFileFullPath, out Dictionary<string, ReportDiagnostic> diagnosticOptions, IList<Diagnostic> diagnosticsOpt, CommonMessageProvider messageProviderOpt)
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 18, 2020

Choose a reason for hiding this comment

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

Suggested change
internal static ReportDiagnostic GetDiagnosticOptionsFromRulesetFile(string? rulesetFileFullPath, out Dictionary<string, ReportDiagnostic> diagnosticOptions, IList<Diagnostic> diagnosticsOpt, CommonMessageProvider messageProviderOpt)
internal static ReportDiagnostic GetDiagnosticOptionsFromRulesetFile(string? rulesetFileFullPath, out Dictionary<string, ReportDiagnostic> diagnosticOptions, IList<Diagnostic>? diagnosticsOpt, CommonMessageProvider? messageProviderOpt)
``` #Resolved

@jcouv jcouv added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Jan 18, 2020
@jcouv jcouv removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Jan 24, 2020
}

if (string.IsNullOrEmpty(filePath))
if (RoslynString.IsNullOrWhiteSpace(filePath))
Copy link
Copy Markdown
Member Author

@jcouv jcouv Jan 24, 2020

Choose a reason for hiding this comment

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

📝 this change aligns with logic in ParseResourceDescription (called a few lines above). The effect is to change which error is reported (see ParseResources test, which was updated). #Resolved

if (!string.IsNullOrEmpty(outputDirectory) && baseDirectory != outputDirectory)
if (RoslynString.IsNullOrEmpty(outputDirectory))
{
AddDiagnostic(diagnostics, ErrorCode.ERR_NoOutputDirectory);
Copy link
Copy Markdown
Member Author

@jcouv jcouv Jan 24, 2020

Choose a reason for hiding this comment

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

📝 this new error guards against null output directory, allowing us to declare OutputDirectory as non-nullable. #Resolved

Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 31, 2020

Choose a reason for hiding this comment

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

📝 This new error means loc change, and the cutoff for 16.5 already passed so this needs to retarget 16.6 Preview 1 #Resolved

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.

What was the previous behavior?


In reply to: 370462374 [](ancestors = 370462374)

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.

You could get a null OutputDirectory, which we're not prepared to handle in code that reads that property.
I'm not sure if that emitting code (which reads the property) could be reached though.


In reply to: 377830798 [](ancestors = 377830798,370462374)

@jcouv jcouv marked this pull request as ready for review January 24, 2020 05:02
@jcouv jcouv requested review from a team as code owners January 24, 2020 05:02
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jan 29, 2020

@dotnet/roslyn-compiler @sharwell for review. This annotates more of the public APIs. Thanks #Resolved

1 similar comment
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jan 31, 2020

@dotnet/roslyn-compiler @sharwell for review. This annotates more of the public APIs. Thanks #Resolved

Copy link
Copy Markdown
Contributor

@sharwell sharwell left a comment

Choose a reason for hiding this comment

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

Marking request changes just for the retarget due to loc change

}

var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName));
var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir!, moduleName));
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 31, 2020

Choose a reason for hiding this comment

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

💡 Replacing the condition above would remove the need for this suppression:

-if (moduleBuilder.Count == 0)
+if (assemblyDir is null)

If that's too confusing, an equally usable alternative is moving the initialization of assemblyDir outside that if statement:

assemblyDir ??= Path.GetDirectoryName(fileKey.FullPath);
``` #Resolved

if (references.IsDefaultOrEmpty && messageProviderOpt != null)
{
diagnosticsOpt.Add(new DiagnosticInfo(messageProviderOpt, messageProviderOpt.ERR_MetadataFileNotFound, cmdReference.Reference));
diagnosticsOpt!.Add(new DiagnosticInfo(messageProviderOpt, messageProviderOpt.ERR_MetadataFileNotFound, cmdReference.Reference));
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 31, 2020

Choose a reason for hiding this comment

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

💭 If you're going to need a suppression either way, why not keep the original check and place the suppression on messageProviderOpt?

Edit: Oh, because two suppressions. In that case, I vote for the original code but add:

RoslynDebug.AssertNotNull(messageProviderOpt);
``` #Resolved

if (!string.IsNullOrEmpty(outputDirectory) && baseDirectory != outputDirectory)
if (RoslynString.IsNullOrEmpty(outputDirectory))
{
AddDiagnostic(diagnostics, ErrorCode.ERR_NoOutputDirectory);
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 31, 2020

Choose a reason for hiding this comment

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

📝 This new error means loc change, and the cutoff for 16.5 already passed so this needs to retarget 16.6 Preview 1 #Resolved

private static IEnumerable<string> ParseUsings(string arg, string? value, IList<Diagnostic> diagnostics)
{
if (value.Length == 0)
if (value is null || value.Length == 0)
Copy link
Copy Markdown
Contributor

@sharwell sharwell Jan 31, 2020

Choose a reason for hiding this comment

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

Suggested change
if (value is null || value.Length == 0)
if (RoslynString.IsNullOrEmpty(value))
``` #Resolved

/// Compilation name or null if not specified.
/// </summary>
public string CompilationName { get; internal set; }
public string? CompilationName { get; internal set; }
Copy link
Copy Markdown
Member

@jaredpar jaredpar Feb 12, 2020

Choose a reason for hiding this comment

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

I distinctly remember fixing a bug that came about specifically because the code didn't realize CompilationName could be null. Seeing this makes me happy for the time I spent fixing that bug some years ago. #Resolved

/// Trims all '.' and whitespace from the end of the path
/// </summary>
internal static string RemoveTrailingSpacesAndDots(string path)
internal static string? RemoveTrailingSpacesAndDots(string? path)
Copy link
Copy Markdown
Member

@jaredpar jaredpar Feb 12, 2020

Choose a reason for hiding this comment

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

Did you consider using NotNullWhenNotNull to mark the return? #Resolved

}

if (responsePaths != null)
if (responsePaths != null &&
Copy link
Copy Markdown
Member

@jaredpar jaredpar Feb 12, 2020

Choose a reason for hiding this comment

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

Feels like we're hiding a bug here. Agree this change is necessary because the old code path would essentially throw in the case where GetDirectoryName returned null. At the same time though now this will just silently continue. Should we issue the diagnostic FTL_InvalidInputFileName when we can't get a directory name here? #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.

Makes sense.
I'll add the diagnostic. I've not been able to hit this case yet though, so it will lack coverage...


In reply to: 378077429 [](ancestors = 378077429)

}

if (path.Length == 1)
if (path!.Length == 1)
Copy link
Copy Markdown
Member

@jaredpar jaredpar Feb 12, 2020

Choose a reason for hiding this comment

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

Seems like path could be null in the case here where kind == PathKind.Empty #Resolved

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.

See assertion I added above. In this branch, we know that GetPathKind(path) == kind == PathKind.RelativeToCurrentDirectory


In reply to: 378078393 [](ancestors = 378078393)

@jaredpar
Copy link
Copy Markdown
Member

jaredpar commented Feb 12, 2020

Done with review pass (iteration 12) #Resolved

@jcouv jcouv requested a review from a team as a code owner February 13, 2020 00:04
[Theory]
[InlineData(null, null)]
[InlineData("c:", null)]
[InlineData("c:\\", null)]
Copy link
Copy Markdown
Contributor

@cston cston Feb 13, 2020

Choose a reason for hiding this comment

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

"c:\" [](start = 20, length = 6)

Are these tests only run on Windows? #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.

I thought the logic in the method didn't depend on the current platform, but it looks like it does (directory separator is \ or /). I'll make the test conditional.


In reply to: 378585981 [](ancestors = 378585981)

[InlineData("\\first\\second", "\\first")]
[InlineData("\\first\\second\\", "\\first\\second")]
[InlineData("\\first\\second\\third", "\\first\\second")]
public void TestGetDirectoryName_Windows(string path, string expectedOutput)
Copy link
Copy Markdown
Contributor

@cston cston Feb 13, 2020

Choose a reason for hiding this comment

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

public [](start = 8, length = 6)

Do we need to test relative paths as well? #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.

Doesn't hurt. I'll add.
The main point of this test is to show when we're returning null


In reply to: 378586494 [](ancestors = 378586494)

@jcouv jcouv dismissed sharwell’s stale review February 14, 2020 00:41

master is now targeting 16.6, so no loc constraint

@333fred
Copy link
Copy Markdown
Member

333fred commented Feb 14, 2020

                    string referencePath = Path.Combine(directory!, referenceName + ".dll");

Consider using an assert instead of a suppression. #Resolved


Refers to: src/Compilers/Core/Portable/AssemblyUtilities.cs:63 in 886d453. [](commit_id = 886d453, deletion_comment = False)

else
{
pdbPath = Path.ChangeExtension(Path.Combine(outputDirectory, outputFileName), ".pdb");
// If outputDirectory were null, then outputFileName would be null (see ParseAndNormalizeFile)
Copy link
Copy Markdown
Member

@333fred 333fred Feb 14, 2020

Choose a reason for hiding this comment

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

These comments are better served with an assert for verification. #Resolved

Copy link
Copy Markdown
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 20) with assertion changes.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Feb 14, 2020

                    string referencePath = Path.Combine(directory!, referenceName + ".dll");

Hum, this is an existing suppression, and I'm not sure it's justified... I'll add to tracking issue for null directories


In reply to: 586411159 [](ancestors = 586411159)


Refers to: src/Compilers/Core/Portable/AssemblyUtilities.cs:63 in 886d453. [](commit_id = 886d453, deletion_comment = False)

@jaredpar
Copy link
Copy Markdown
Member

@jcouv @333fred

Hum, this is an existing suppression, and I'm not sure it's justified...

It's justified. The rational is captured here #41485 (comment)

@jaredpar
Copy link
Copy Markdown
Member

@jcouv @333fred

At the same time though several people have asked about why this suppression exists. Clearly the context is unclear. At the least I should add a comment here to justify why this is done this way.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Feb 15, 2020

@jaredpar I’ll update the comment

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Feb 15, 2020

@jaredpar I'm going to leave the comment as-is for follow-up. Reading the rationale again, I suspect that it is incorrect. If you open a file in the root directory, the OpenRead would succeed, but GetDirectoryName would return null (that's really annoying behavior in top-level directory in my opinion, but that seems to be the case).

I'll go ahead and merge leaving the follow-up comment.

@jcouv jcouv merged commit 092d850 into dotnet:master Feb 15, 2020
@jcouv jcouv deleted the annotate-cmd branch February 15, 2020 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Concept-Null Annotations The issue involves annotating an API for nullable reference types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants