Skip to content

Commit c4ae0a5

Browse files
committed
Merge pull request #229 from PowerShell/BugFixes
Take BugFixes to Master
2 parents 46fcb59 + 11507fc commit c4ae0a5

File tree

4 files changed

+154
-109
lines changed

4 files changed

+154
-109
lines changed

Engine/Helper.cs

Lines changed: 35 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -859,102 +859,64 @@ public Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(string
859859
List<RuleSuppression> ruleSuppressions = ruleSuppressionsDict[ruleName];
860860

861861
int recordIndex = 0;
862-
int ruleSuppressionIndex = 0;
863-
DiagnosticRecord record = diagnostics.First();
864-
RuleSuppression ruleSuppression = ruleSuppressions.First();
865-
int suppressionCount = 0;
862+
int startRecord = 0;
863+
bool[] suppressed = new bool[diagnostics.Count];
866864

867-
while (recordIndex < diagnostics.Count)
865+
foreach (RuleSuppression ruleSuppression in ruleSuppressions)
868866
{
869-
if (!String.IsNullOrWhiteSpace(ruleSuppression.Error))
867+
int suppressionCount = 0;
868+
while (startRecord < diagnostics.Count && diagnostics[startRecord].Extent.StartOffset < ruleSuppression.StartOffset)
870869
{
871-
ruleSuppressionIndex += 1;
870+
startRecord += 1;
871+
}
872+
873+
// at this point, start offset of startRecord is greater or equals to rulesuppression.startoffset
874+
recordIndex = startRecord;
875+
876+
while (recordIndex < diagnostics.Count)
877+
{
878+
DiagnosticRecord record = diagnostics[recordIndex];
872879

873-
if (ruleSuppressionIndex == ruleSuppressions.Count)
880+
if (record.Extent.EndOffset > ruleSuppression.EndOffset)
874881
{
875882
break;
876883
}
877884

878-
ruleSuppression = ruleSuppressions[ruleSuppressionIndex];
879-
suppressionCount = 0;
880-
881-
continue;
882-
}
883-
884-
// if the record precedes the rule suppression then we don't apply the suppression
885-
// so we check that start of record is greater than start of suppression
886-
if (record.Extent.StartOffset >= ruleSuppression.StartOffset)
887-
{
888-
// end of the rule suppression is less than the record start offset so move on to next rule suppression
889-
if (ruleSuppression.EndOffset < record.Extent.StartOffset)
885+
if (string.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID))
890886
{
891-
ruleSuppressionIndex += 1;
892-
893-
// If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
894-
if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
895-
{
896-
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
897-
System.IO.Path.GetFileName(record.Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
898-
Helper.Instance.MyCmdlet.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
899-
}
900-
901-
if (ruleSuppressionIndex == ruleSuppressions.Count)
902-
{
903-
break;
904-
}
905-
906-
ruleSuppression = ruleSuppressions[ruleSuppressionIndex];
907-
suppressionCount = 0;
908-
909-
continue;
887+
suppressed[recordIndex] = true;
888+
suppressionCount += 1;
910889
}
911-
// at this point, the record is inside the interval
912890
else
913891
{
914-
// if the rule suppression id from the rule suppression is not null and the one from diagnostic record is not null
915-
// and they are they are not the same then we cannot ignore the record
916-
if (!string.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && !string.IsNullOrWhiteSpace(record.RuleSuppressionID)
917-
&& !string.Equals(ruleSuppression.RuleSuppressionID, record.RuleSuppressionID, StringComparison.OrdinalIgnoreCase))
918-
{
919-
suppressionCount -= 1;
920-
unSuppressedRecords.Add(record);
921-
}
922-
// otherwise, we suppress the record, move on to the next.
923-
else
892+
//if there is a rule suppression id, we only suppressed if it matches
893+
if (!String.IsNullOrWhiteSpace(record.RuleSuppressionID) &&
894+
string.Equals(ruleSuppression.RuleSuppressionID, record.RuleSuppressionID, StringComparison.OrdinalIgnoreCase))
924895
{
896+
suppressed[recordIndex] = true;
925897
suppressedRecords.Add(new SuppressedRecord(record, ruleSuppression));
898+
suppressionCount += 1;
926899
}
927900
}
928-
}
929-
else
930-
{
931-
unSuppressedRecords.Add(record);
932-
}
933901

934-
// important assumption: this point is reached only if we want to move to the next record
935-
recordIndex += 1;
936-
suppressionCount += 1;
902+
recordIndex += 1;
903+
}
937904

938-
if (recordIndex == diagnostics.Count)
905+
// If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
906+
if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
939907
{
940-
// If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
941-
if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
942-
{
943-
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
944-
System.IO.Path.GetFileName(record.Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
945-
Helper.Instance.MyCmdlet.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
946-
}
947-
948-
break;
908+
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
909+
System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
910+
Helper.Instance.MyCmdlet.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
949911
}
950-
951-
record = diagnostics[recordIndex];
952912
}
953913

954-
while (recordIndex < diagnostics.Count)
914+
for (int i = 0; i < suppressed.Length; i += 1)
955915
{
956-
unSuppressedRecords.Add(diagnostics[recordIndex]);
957-
recordIndex += 1;
916+
if (!suppressed[i])
917+
{
918+
unSuppressedRecords.Add(diagnostics[i]);
919+
}
958920
}
959921

960922
return result;

Engine/VariableAnalysisBase.cs

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -405,15 +405,36 @@ public class Block
405405
internal bool _returns;
406406
internal bool _unreachable;
407407

408+
// Only Entry block, that can be constructed via NewEntryBlock() is reachable initially.
409+
// all other blocks are unreachable.
410+
// reachability of block should be proved with FlowsTo() calls.
411+
public Block()
412+
{
413+
this._unreachable = true;
414+
}
415+
416+
public static Block NewEntryBlock()
417+
{
418+
return new Block(unreachable: false);
419+
}
420+
421+
private Block(bool unreachable)
422+
{
423+
this._unreachable = unreachable;
424+
}
425+
426+
/// <summary>
427+
/// Tell flow analysis that this block can flow to next block.
428+
/// </summary>
429+
/// <param name="next"></param>
408430
internal void FlowsTo(Block next)
409431
{
410432
if (_successors.IndexOf(next) < 0)
411433
{
412-
if (_unreachable)
434+
if (!_unreachable)
413435
{
414-
next._unreachable = true;
436+
next._unreachable = false;
415437
}
416-
417438
_successors.Add(next);
418439
next._predecessors.Add(this);
419440
}
@@ -725,6 +746,25 @@ internal static void RenameVariables(Block block)
725746
internal static void InitializeSSA(Dictionary<string, VariableAnalysisDetails> VariableAnalysis, Block Entry, List<Block> Blocks)
726747
{
727748
VariablesDictionary = new Dictionary<string, VariableAnalysisDetails>(StringComparer.OrdinalIgnoreCase);
749+
750+
foreach (var block in Blocks)
751+
{
752+
List<Block> _unreachables = new List<Block>();
753+
foreach (var pred in block._predecessors)
754+
{
755+
if (pred._unreachable)
756+
{
757+
_unreachables.Add(pred);
758+
pred._successors.Remove(block);
759+
}
760+
}
761+
762+
foreach (var pred in _unreachables)
763+
{
764+
block._predecessors.Remove(pred);
765+
}
766+
}
767+
728768
InternalVariablesDictionary.Clear();
729769
SSADictionary.Clear();
730770
Counters.Clear();
@@ -1877,7 +1917,7 @@ public Block Exit
18771917
/// </summary>
18781918
public void Init()
18791919
{
1880-
_entryBlock = new Block();
1920+
_entryBlock = Block.NewEntryBlock();
18811921
_exitBlock = new Block();
18821922
}
18831923

@@ -2057,6 +2097,13 @@ public object VisitIfStatement(IfStatementAst ifStmtAst)
20572097
if (ifStmtAst == null) return null;
20582098

20592099
Block afterStmt = new Block();
2100+
2101+
if (ifStmtAst.ElseClause == null)
2102+
{
2103+
// There is no else, flow can go straight to afterStmt.
2104+
_currentBlock.FlowsTo(afterStmt);
2105+
}
2106+
20602107
int clauseCount = ifStmtAst.Clauses.Count;
20612108
for (int i = 0; i < clauseCount; i++)
20622109
{
@@ -2403,52 +2450,62 @@ public object VisitTryStatement(TryStatementAst tryStatementAst)
24032450

24042451
tryStatementAst.Body.Visit(this.Decorator);
24052452

2406-
// This is either the first block in the finally, or the first block after all the catches if there is no finally.
2407-
// For return analysis, we start off assuming this block is reachable only if the end of the try body
2408-
// is reachable, but that can change if we find a catch that falls through.
2409-
var afterTry = new Block { _unreachable = _currentBlock._unreachable };
2453+
Block lastBlockInTry = _currentBlock;
2454+
var finallyFirstBlock = tryStatementAst.Finally == null ? null : new Block();
2455+
Block finallyLastBlock = null;
2456+
2457+
// This is the first block after all the catches and finally (if present).
2458+
var afterTry = new Block();
2459+
2460+
bool isCatchAllPresent = false;
24102461

24112462
foreach (var catchAst in tryStatementAst.CatchClauses)
24122463
{
2464+
if (catchAst.IsCatchAll)
2465+
{
2466+
isCatchAllPresent = true;
2467+
}
2468+
24132469
// Any statement in the try block could throw and reach the catch, so assume the worst (from a data
24142470
// flow perspective) and make the predecessor to the catch the block before entering the try.
24152471
_currentBlock = new Block();
24162472
blockBeforeTry.FlowsTo(_currentBlock);
2417-
24182473
catchAst.Visit(this.Decorator);
2419-
2420-
if (!_currentBlock._unreachable)
2421-
{
2422-
// The last block of the catch falls through, so we can get reach blocks after the try.
2423-
afterTry._unreachable = false;
2424-
}
2474+
_currentBlock.FlowsTo(finallyFirstBlock ?? afterTry);
24252475
}
24262476

2427-
// Assume nothing in the try was executed, so flow comes from before the try. We could have the catch blocks
2428-
// also flow to this block, but that won't improve the analysis any, so skip that.
2429-
_currentBlock = afterTry;
2430-
blockBeforeTry.FlowsTo(_currentBlock);
2431-
2432-
if (tryStatementAst.Finally != null)
2477+
if (finallyFirstBlock != null)
24332478
{
2479+
lastBlockInTry.FlowsTo(finallyFirstBlock);
2480+
2481+
_currentBlock = finallyFirstBlock;
24342482
tryStatementAst.Finally.Visit(this.Decorator);
2483+
_currentBlock.FlowsTo(afterTry);
2484+
2485+
finallyLastBlock = _currentBlock;
24352486

2436-
if (!_currentBlock._unreachable)
2487+
// For finally block, there are 2 cases: when try-body throw and when it doesn't.
2488+
// For these two cases value of 'finallyLastBlock._throws' would be different.
2489+
if (!isCatchAllPresent)
24372490
{
2438-
// If the finally throws (it can't have other flow like return, break, or continue)
2439-
// then after the try/catch/finally is unreachable, otherwise it is reachable.
2440-
afterTry._unreachable = false;
2491+
// This flow exist only, if there is no catch for all exceptions.
2492+
blockBeforeTry.FlowsTo(finallyFirstBlock);
2493+
2494+
var rethrowAfterFinallyBlock = new Block();
2495+
finallyLastBlock.FlowsTo(rethrowAfterFinallyBlock);
2496+
rethrowAfterFinallyBlock._throws = true;
2497+
rethrowAfterFinallyBlock.FlowsTo(_exitBlock);
24412498
}
2442-
// We can assume that flow from the finally reaches the code following the finally. Of course, if an exception occurs,
2443-
// then the code can't be reached, but our conservative data flow modeling assures us correctness, the exception will
2444-
// either leave the current function (meaning it doesn't matter that we assumed flow reached after the finally), or
2445-
// it will be caught and handled by a containing catch, and our conversative data flow ensures the correctness of the
2446-
// generated code.
24472499

2448-
var newBlock = new Block();
2449-
_currentBlock.FlowsTo(newBlock);
2450-
_currentBlock = newBlock;
2500+
// This flow always exists.
2501+
finallyLastBlock.FlowsTo(afterTry);
24512502
}
2503+
else
2504+
{
2505+
lastBlockInTry.FlowsTo(afterTry);
2506+
}
2507+
2508+
_currentBlock = afterTry;
24522509

24532510
return null;
24542511
}
@@ -2486,7 +2543,7 @@ where t.Label.Equals(labelStrAst.Value, StringComparison.OrdinalIgnoreCase)
24862543
}
24872544

24882545
// The next block is unreachable, but is necessary to keep the flow graph correct.
2489-
_currentBlock = new Block { _unreachable = true };
2546+
_currentBlock = new Block();
24902547
}
24912548

24922549
/// <summary>
@@ -2525,7 +2582,7 @@ internal Block ControlFlowStatement(PipelineBaseAst pipelineAst)
25252582
var lastBlockInStatement = _currentBlock;
25262583

25272584
// The next block is unreachable, but is necessary to keep the flow graph correct.
2528-
_currentBlock = new Block { _unreachable = true };
2585+
_currentBlock = new Block();
25292586
return lastBlockInStatement;
25302587
}
25312588

Tests/Engine/RuleSuppression.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ function SuppressMe ()
1414

1515
}
1616

17+
function SuppressTwoVariables()
18+
{
19+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUninitializedVariable", "b")]
20+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUninitializedVariable", "a")]
21+
Param([string]$a, [int]$b)
22+
{
23+
}
24+
}
25+
1726
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Scope="Class")]
1827
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("pSAvoidUsingInvokeExpression", "")]
1928
class TestClass

Tests/Rules/AvoidGlobalOrUnitializedVarsNoViolations.ps1

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,21 @@ function Test-PreferenceVariable
4141
}
4242

4343
$VerbosePreference
44-
}
44+
}
45+
46+
function Test-Throw
47+
{
48+
if ($true)
49+
{
50+
throw "First time"
51+
}
52+
53+
$a = 4
54+
55+
if ($false)
56+
{
57+
throw "Second time"
58+
}
59+
60+
$a
61+
}

0 commit comments

Comments
 (0)