Skip to content

Commit 221933f

Browse files
committed
Merge pull request #185 from PowerShell/BugFixes
Bug fixes
2 parents 2849549 + f6ba095 commit 221933f

8 files changed

+128
-64
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ public SwitchParameter SuppressedOnly
129129
#region Private Members
130130

131131
Dictionary<string, List<string>> validationResults = new Dictionary<string, List<string>>();
132-
private ScriptBlockAst ast = null;
133-
private IEnumerable<IRule> rules = null;
132+
private ScriptBlockAst ast = null;
133+
private IEnumerable<IRule> rules = null;
134134

135135
#endregion
136136

@@ -163,9 +163,9 @@ protected override void BeginProcessing()
163163
}
164164
else
165165
{
166-
validationResults.Add("InvalidPaths", new List<string>());
166+
validationResults.Add("InvalidPaths", new List<string>());
167167
validationResults.Add("ValidModPaths", new List<string>());
168-
validationResults.Add("ValidDllPaths", new List<string>());
168+
validationResults.Add("ValidDllPaths", new List<string>());
169169
}
170170

171171
#endregion
@@ -174,7 +174,7 @@ protected override void BeginProcessing()
174174

175175
try
176176
{
177-
if (validationResults["ValidDllPaths"].Count == 0 &&
177+
if (validationResults["ValidDllPaths"].Count == 0 &&
178178
validationResults["ValidModPaths"].Count == 0)
179179
{
180180
ScriptAnalyzer.Instance.Initialize();
@@ -185,7 +185,7 @@ protected override void BeginProcessing()
185185
}
186186
}
187187
catch (Exception ex)
188-
{
188+
{
189189
ThrowTerminatingError(new ErrorRecord(ex, ex.HResult.ToString("X", CultureInfo.CurrentCulture),
190190
ErrorCategory.NotSpecified, this));
191191
}
@@ -228,8 +228,8 @@ private void ProcessPath(string path)
228228

229229
if (path == null)
230230
{
231-
ThrowTerminatingError(new ErrorRecord(new FileNotFoundException(),
232-
string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, path),
231+
ThrowTerminatingError(new ErrorRecord(new FileNotFoundException(),
232+
string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, path),
233233
ErrorCategory.InvalidArgument, this));
234234
}
235235

@@ -315,11 +315,11 @@ private void AnalyzeFile(string filePath)
315315
else
316316
{
317317
ThrowTerminatingError(new ErrorRecord(new FileNotFoundException(),
318-
string.Format(CultureInfo.CurrentCulture, Strings.InvalidPath, filePath),
318+
string.Format(CultureInfo.CurrentCulture, Strings.InvalidPath, filePath),
319319
ErrorCategory.InvalidArgument, filePath));
320320
}
321321

322-
if (errors != null && errors.Length > 0)
322+
if (errors != null && errors.Length > 0)
323323
{
324324
foreach (ParseError error in errors)
325325
{
@@ -362,7 +362,7 @@ private void AnalyzeFile(string filePath)
362362
#region Run ScriptRules
363363
//Trim down to the leaf element of the filePath and pass it to Diagnostic Record
364364
string fileName = System.IO.Path.GetFileName(filePath);
365-
365+
366366
if (ScriptAnalyzer.Instance.ScriptRules != null)
367367
{
368368
foreach (IScriptRule scriptRule in ScriptAnalyzer.Instance.ScriptRules)
@@ -433,7 +433,7 @@ private void AnalyzeFile(string filePath)
433433
break;
434434
}
435435
}
436-
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch))
436+
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch))
437437
{
438438
WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, tokenRule.GetName()));
439439

@@ -448,7 +448,7 @@ private void AnalyzeFile(string filePath)
448448
catch (Exception tokenRuleException)
449449
{
450450
WriteError(new ErrorRecord(tokenRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, fileName));
451-
}
451+
}
452452
}
453453
}
454454
}
@@ -458,46 +458,50 @@ private void AnalyzeFile(string filePath)
458458
#region DSC Resource Rules
459459
if (ScriptAnalyzer.Instance.DSCResourceRules != null)
460460
{
461-
// Run DSC Class rule
462-
foreach (IDSCResourceRule dscResourceRule in ScriptAnalyzer.Instance.DSCResourceRules)
461+
// Invoke AnalyzeDSCClass only if the ast is a class based resource
462+
if (Helper.Instance.IsDscResourceClassBased(ast))
463463
{
464-
bool includeRegexMatch = false;
465-
bool excludeRegexMatch = false;
466-
467-
foreach (Regex include in includeRegexList)
464+
// Run DSC Class rule
465+
foreach (IDSCResourceRule dscResourceRule in ScriptAnalyzer.Instance.DSCResourceRules)
468466
{
469-
if (include.IsMatch(dscResourceRule.GetName()))
467+
bool includeRegexMatch = false;
468+
bool excludeRegexMatch = false;
469+
470+
foreach (Regex include in includeRegexList)
470471
{
471-
includeRegexMatch = true;
472-
break;
472+
if (include.IsMatch(dscResourceRule.GetName()))
473+
{
474+
includeRegexMatch = true;
475+
break;
476+
}
473477
}
474-
}
475478

476-
foreach (Regex exclude in excludeRegexList)
477-
{
478-
if (exclude.IsMatch(dscResourceRule.GetName()))
479+
foreach (Regex exclude in excludeRegexList)
479480
{
480-
excludeRegexMatch = true;
481-
break;
481+
if (exclude.IsMatch(dscResourceRule.GetName()))
482+
{
483+
excludeRegexMatch = true;
484+
break;
485+
}
482486
}
483-
}
484-
485-
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || excludeRegexMatch))
486-
{
487-
WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName()));
488487

489-
// Ensure that any unhandled errors from Rules are converted to non-terminating errors
490-
// We want the Engine to continue functioning even if one or more Rules throws an exception
491-
try
488+
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || excludeRegexMatch))
492489
{
493-
var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCClass(ast, filePath).ToList());
494-
diagnostics.AddRange(records.Item2);
495-
suppressed.AddRange(records.Item1);
490+
WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName()));
491+
492+
// Ensure that any unhandled errors from Rules are converted to non-terminating errors
493+
// We want the Engine to continue functioning even if one or more Rules throws an exception
494+
try
495+
{
496+
var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCClass(ast, filePath).ToList());
497+
diagnostics.AddRange(records.Item2);
498+
suppressed.AddRange(records.Item1);
499+
}
500+
catch (Exception dscResourceRuleException)
501+
{
502+
WriteError(new ErrorRecord(dscResourceRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, filePath));
503+
}
496504
}
497-
catch (Exception dscResourceRuleException)
498-
{
499-
WriteError(new ErrorRecord(dscResourceRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, filePath));
500-
}
501505
}
502506
}
503507

@@ -543,7 +547,7 @@ private void AnalyzeFile(string filePath)
543547
}
544548
}
545549

546-
}
550+
}
547551
}
548552
#endregion
549553

@@ -607,4 +611,4 @@ private void AnalyzeFile(string filePath)
607611

608612
#endregion
609613
}
610-
}
614+
}

Engine/Helper.cs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,35 @@ public bool IsDscResourceModule(string filePath)
191191
return false;
192192
}
193193

194+
/// <summary>
195+
/// Given an AST, checks whether dsc resource is class based or not
196+
/// </summary>
197+
/// <param name="ast"></param>
198+
/// <returns></returns>
199+
public bool IsDscResourceClassBased(ScriptBlockAst ast)
200+
{
201+
if (null == ast)
202+
{
203+
return false;
204+
}
205+
206+
List<string> dscResourceFunctionNames = new List<string>(new string[] { "Test", "Get", "Set" });
207+
208+
IEnumerable<Ast> dscClasses = ast.FindAll(item =>
209+
item is TypeDefinitionAst
210+
&& ((item as TypeDefinitionAst).IsClass)
211+
&& (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true);
212+
213+
// Found one or more classes marked with DscResource attribute
214+
// So this might be a DscResource. Further validation will be performed by the individual rules
215+
if (null != dscClasses && 0 < dscClasses.Count())
216+
{
217+
return true;
218+
}
219+
220+
return false;
221+
}
222+
194223
/// <summary>
195224
/// Given a commandast, checks whether positional parameters are used or not.
196225
/// </summary>
@@ -280,7 +309,7 @@ public bool PositionalParameterUsed(CommandAst cmdAst)
280309
/// <param name="name"></param>
281310
/// <param name="commandType"></param>
282311
/// <returns></returns>
283-
public CommandInfo GetCommandInfo(string name, CommandTypes commandType=CommandTypes.All)
312+
public CommandInfo GetCommandInfo(string name, CommandTypes commandType = CommandTypes.All)
284313
{
285314
return Helper.Instance.MyCmdlet.InvokeCommand.GetCommand(name, commandType);
286315
}
@@ -294,7 +323,7 @@ public IEnumerable<Ast> DscResourceFunctions(Ast ast)
294323
{
295324
List<string> resourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Get-TargetResource", "Test-TargetResource" });
296325
return ast.FindAll(item => item is FunctionDefinitionAst
297-
&& resourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
326+
&& resourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
298327
}
299328

300329
/// <summary>
@@ -481,7 +510,7 @@ public bool AllCodePathReturns(Ast ast)
481510
var analysis = VariableAnalysisDictionary[ast];
482511
return analysis.Exit._predecessors.All(block => block._returns || block._unreachable || block._throws);
483512
}
484-
513+
485514
/// <summary>
486515
/// Initialize variable analysis on the script ast
487516
/// </summary>
@@ -701,7 +730,7 @@ public Dictionary<string, List<RuleSuppression>> GetRuleSuppression(Ast ast)
701730
{
702731
ruleSuppressionList.AddRange(RuleSuppression.GetSuppressions(sbAst.ParamBlock.Attributes, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset, sbAst));
703732
}
704-
733+
705734
// Get rule suppression from functions
706735
IEnumerable<FunctionDefinitionAst> funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true).Cast<FunctionDefinitionAst>();
707736

@@ -727,13 +756,13 @@ public Dictionary<string, List<RuleSuppression>> GetRuleSuppression(Ast ast)
727756
List<RuleSuppression> ruleSuppressions = new List<RuleSuppression>();
728757
results.Add(ruleSuppression.RuleName, ruleSuppressions);
729758
}
730-
759+
731760
results[ruleSuppression.RuleName].Add(ruleSuppression);
732761
}
733762

734763
return results;
735764
}
736-
765+
737766
/// <summary>
738767
/// Returns a list of rule suppressions from the function
739768
/// </summary>
@@ -943,11 +972,11 @@ public int Compare(Tuple<int, int> t1, Tuple<int, int> t2)
943972
}
944973
}
945974
}
946-
975+
947976
/// <summary>
948977
/// Class used to do variable analysis on the whole script
949978
/// </summary>
950-
public class ScriptAnalysis: ICustomAstVisitor
979+
public class ScriptAnalysis : ICustomAstVisitor
951980
{
952981
private VariableAnalysis OuterAnalysis;
953982

@@ -1148,7 +1177,7 @@ public object VisitBreakStatement(BreakStatementAst breakStatementAst)
11481177
{
11491178
return null;
11501179
}
1151-
1180+
11521181
/// <summary>
11531182
/// Visits body
11541183
/// </summary>
@@ -2396,7 +2425,7 @@ public object VisitArrayExpression(ArrayExpressionAst arrayExprAst)
23962425
{
23972426
return typeof(System.Array).FullName;
23982427
}
2399-
2428+
24002429
/// <summary>
24012430
/// Returns type of array
24022431
/// </summary>
@@ -2499,7 +2528,7 @@ public object VisitIndexExpression(IndexExpressionAst indexAst)
24992528

25002529
return null;
25012530
}
2502-
2531+
25032532
/// <summary>
25042533
/// Only returns boolean type for unary operator that returns boolean
25052534
/// </summary>
@@ -2540,4 +2569,4 @@ public object VisitUsingExpression(UsingExpressionAst usingExpressionAst)
25402569
return null;
25412570
}
25422571
}
2543-
}
2572+
}

Engine/VariableAnalysis.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private void ProcessParameters(IEnumerable<ParameterAst> parameters)
8686
type = paramAst.TypeName.GetReflectionType();
8787
}
8888

89-
if (String.Equals(paramAst.TypeName.FullName, "switch", StringComparison.OrdinalIgnoreCase))
89+
if (paramAst.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter))
9090
{
9191
isSwitchOrMandatory = true;
9292
}

Rules/AvoidUsingInternalURLs.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4444
{
4545
foreach (StringConstantExpressionAst expressionAst in expressionAsts)
4646
{
47+
//Check if XPath is used. If XPath is used, then we don't throw warnings.
48+
Ast parentAst = expressionAst.Parent;
49+
if (parentAst is InvokeMemberExpressionAst)
50+
{
51+
InvokeMemberExpressionAst invocation = parentAst as InvokeMemberExpressionAst;
52+
if (invocation != null)
53+
{
54+
if (String.Equals(invocation.Member.ToString(), "SelectSingleNode",StringComparison.OrdinalIgnoreCase) ||
55+
String.Equals(invocation.Member.ToString(), "SelectNodes",StringComparison.OrdinalIgnoreCase) ||
56+
String.Equals(invocation.Member.ToString(), "Select", StringComparison.OrdinalIgnoreCase) ||
57+
String.Equals(invocation.Member.ToString(), "Evaluate",StringComparison.OrdinalIgnoreCase) ||
58+
String.Equals(invocation.Member.ToString(), "Matches",StringComparison.OrdinalIgnoreCase))
59+
{
60+
continue;
61+
}
62+
63+
}
64+
}
65+
66+
4767
bool isPathValid = false;
4868
bool isInternalURL = false;
4969
//make sure there is no path
@@ -128,7 +148,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
128148
new DiagnosticRecord(
129149
String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingInternalURLsError,
130150
expressionAst.Value), expressionAst.Extent,
131-
GetName(), DiagnosticSeverity.Warning, fileName);
151+
GetName(), DiagnosticSeverity.Information, fileName);
132152

133153
}
134154
}
@@ -177,7 +197,7 @@ public SourceType GetSourceType()
177197
/// <returns></returns>
178198
public RuleSeverity GetSeverity()
179199
{
180-
return RuleSeverity.Warning;
200+
return RuleSeverity.Information;
181201
}
182202

183203
/// <summary>

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Describe "TestSeverity" {
118118

119119
It "filters rules based on multiple severity inputs"{
120120
$rules = Get-ScriptAnalyzerRule -Severity Error,Information
121-
$rules.Count | Should be 12
121+
$rules.Count | Should be 13
122122
}
123123

124124
It "takes lower case inputs" {
@@ -137,4 +137,4 @@ Describe "TestWildCard" {
137137
$rules = Get-ScriptAnalyzerRule -Name PSDSC* -Severity Information
138138
$rules.Count | Should be 3
139139
}
140-
}
140+
}

0 commit comments

Comments
 (0)