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

Fix formatter crash when script contains parse errors #728

Merged
merged 3 commits into from
Aug 28, 2018

Conversation

rjmholt
Copy link
Contributor

@rjmholt rjmholt commented Aug 22, 2018

Addresses PowerShell/PSScriptAnalyzer#1057 and PowerShell/PSScriptAnalyzer#826 (comment).

When the formatter was run, it we would try to format even a document with parse errors. Now if there are parse errors, the formatter will do nothing (silently). Ideally we should make VSCode report that formatting failed and why, although this would mean returning a different format result.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

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

Looking good. Just a couple questions before I sign off


if (result == null)
{
_logger.Write(LogLevel.Error, $"Formatter returned null result");
Copy link
Member

Choose a reason for hiding this comment

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

Nit: do you need the $? It doesn't seem to be a template string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah started out writing a different message

/// Wraps the result of an execution of PowerShell to send back through
/// asynchronous calls.
/// </summary>
private class PowerShellResult
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this class could be put in some utility class rather than in this file - perhaps if InvokePowerShellAsync is used elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, but InvokePowerShellAsync is a method on ScriptAnalysis. I didn't want to engage in heavy refactoring

@@ -479,10 +526,12 @@ private PSObject[] InvokePowerShell(string command, IDictionary<string, object>
powerShell.AddParameter(kvp.Key, kvp.Value);
}

var result = new PSObject[0];
PowerShellResult result = null;
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be set to null? Just want to make sure we don't cause a null pointer exception somewhere else :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it has to be set to something here, and creating a new PowerShell result would require creating two new arrays as well. Doesn't seem right.

I think I've dealt with the possibility that any result here could return null at the call site.

But, perhaps it's better to do something here like throw an analysis exception, or something else we catch??

result = powerShell.Invoke()?.ToArray();
PSObject[] output = powerShell.Invoke()?.ToArray();
ErrorRecord[] errors = powerShell.Streams?.Error?.ToArray();
result = new PowerShellResult(output, errors, powerShell.HadErrors);
Copy link
Member

Choose a reason for hiding this comment

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

I notice that both output and errors could potentially be set to null here... Is that going to be ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like we think they can't actually be null. Would be so much nicer if the type system were able to track that...

@@ -342,7 +387,7 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
else
{
// Return an empty marker list
return new ScriptFileMarker[0];
return s_emptyScriptMarkerResult;
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -360,7 +405,7 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
else
{
// Return an empty marker list
return new ScriptFileMarker[0];
return s_emptyScriptMarkerResult;
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@rkeithhill rkeithhill left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

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

LGTM, just a couple things to look at.

/// Defines the list of Script Analyzer rules to include by default if
/// no settings file is specified.
/// </summary>
private static readonly string[] s_includedRules = new string[]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't know how everyone feels about this, but you can ditch the new string[] and just keep the braces for a field initialization. e.g

private static readonly string[] s_includedRules =
{
    "MyRules",
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oooh!

/// <summary>
/// An empty diagnostic result to return when a script fails analysis.
/// </summary>
private static readonly PSObject[] s_emptyDiagnosticResult = new PSObject[0];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we use Array.Empty<PSObject>() here? That way the empty array will be cached (or pulled from cache)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would prefer that -- so long as .NET 4.5.1 supports that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it doesn't.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Aww :/

Probably not worth setting up then since we'll get it when we move to standard.

/// <summary>
/// An empty script marker result to return when no script markers can be returned.
/// </summary>
private static readonly ScriptFileMarker[] s_emptyScriptMarkerResult = new ScriptFileMarker[0];
Copy link
Collaborator

Choose a reason for hiding this comment

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

echo: Array.Empty<ScriptFileMarker>()

try
{
result = powerShell.Invoke()?.ToArray();
PSObject[] output = powerShell.Invoke()?.ToArray();
ErrorRecord[] errors = powerShell.Streams?.Error?.ToArray();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be able to drop the null conditionals, as far as I know Streams and Error are always populated.

Come to think of it, I don't think powerShell.Invoke() can return null either, just an empty collection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I was hoping for

@@ -515,22 +527,25 @@ private void LogAvailablePssaFeatures()
return diagnosticRecords;
}

private PowerShellResult InvokePowerShell(string command, IDictionary<string, object> paramArgMap)
private PowerShellResult InvokePowerShell(string command, IDictionary<string, object> paramArgMap = null)
Copy link
Member

Choose a reason for hiding this comment

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

the default param could be an empty dictionary and then you don't need he null check on line 536

Copy link
Contributor Author

@rjmholt rjmholt Aug 27, 2018

Choose a reason for hiding this comment

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

Yes I made this change because one of the calls to this method had to create a new empty dictionary just to call the method. A null check is much cheaper than creating a new dictionary.

I think the question boils down to "do we make the caller or the callee handle the complexity?". Since a caller could always pass in null for the dictionary (and we didn't handle it), I think defaulting to null and making the callee handle it is probably more efficient, safer and easier to use.

@@ -552,7 +567,7 @@ private PowerShellResult InvokePowerShell(string command, IDictionary<string, ob
}
}

private async Task<PowerShellResult> InvokePowerShellAsync(string command, IDictionary<string, object> paramArgMap)
private async Task<PowerShellResult> InvokePowerShellAsync(string command, IDictionary<string, object> paramArgMap = null)
Copy link
Member

Choose a reason for hiding this comment

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

consider an empty dictionary instead of null

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

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

LGTM just a small nit

@rjmholt rjmholt merged commit 8e29349 into PowerShell:master Aug 28, 2018
@rjmholt rjmholt deleted the fix-formatter-crash branch December 12, 2018 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants