Skip to content

Commit be47a8e

Browse files
author
Quoc Truong
committed
Merge pull request #227 from PowerShell/FixAvoidUninitializedVariableFlowGraph
Fix a bug in the flow graphs for throw statements
2 parents 962246d + 24d4515 commit be47a8e

File tree

2 files changed

+110
-36
lines changed

2 files changed

+110
-36
lines changed

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/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)