Skip to content

Commit f400187

Browse files
committed
Update Fix
1 parent 9224704 commit f400187

6 files changed

+193
-1
lines changed

source/slang/slang-diagnostic-defs.h

+1
Original file line numberDiff line numberDiff line change
@@ -2179,6 +2179,7 @@ DIAGNOSTIC(
21792179
DIAGNOSTIC(41000, Warning, unreachableCode, "unreachable code detected")
21802180
DIAGNOSTIC(41001, Error, recursiveType, "type '$0' contains cyclic reference to itself.")
21812181

2182+
DIAGNOSTIC(41008, Warning, potentialInfiniteLoop, "loop might execute indefinitely")
21822183
DIAGNOSTIC(
21832184
41009,
21842185
Error,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// slang-ir-potential-infinite-loop.cpp
2+
#include "slang-ir-potential-infinite-loop.h"
3+
4+
#include "core/slang-basic.h"
5+
#include "core/slang-dictionary.h" // For HashSet used in DFS
6+
#include "core/slang-io.h"
7+
#include "core/slang-list.h" // For List used in DFS
8+
#include "core/slang-type-text-util.h"
9+
#include "slang-compiler.h"
10+
#include "slang-diagnostics.h"
11+
#include "slang-ir-insts.h"
12+
#include "slang-ir.h"
13+
14+
namespace Slang
15+
{
16+
17+
/// Check if a loop body has any path that can reach the break block
18+
static bool canLoopExit(IRLoop* loop)
19+
{
20+
IRBlock* targetBlock = loop->getTargetBlock();
21+
IRBlock* breakBlock = loop->getBreakBlock();
22+
23+
// If there's no break block, the loop cannot exit
24+
if (!breakBlock)
25+
{
26+
return false;
27+
}
28+
29+
// Fast check: direct branch from header to break block
30+
for (auto inst : targetBlock->getChildren())
31+
{
32+
if (auto branch = as<IRConditionalBranch>(inst))
33+
{
34+
if (branch->getTrueBlock() == breakBlock || branch->getFalseBlock() == breakBlock)
35+
return true;
36+
}
37+
else if (auto unconditionalBranch = as<IRUnconditionalBranch>(inst))
38+
{
39+
if (unconditionalBranch->getTargetBlock() == breakBlock)
40+
return true;
41+
}
42+
}
43+
44+
// Comprehensive but bounded DFS: explore blocks reachable from the loop header
45+
List<IRBlock*> workList;
46+
HashSet<IRBlock*> visited;
47+
48+
auto enqueue = [&](IRBlock* b)
49+
{
50+
if (b && visited.add(b))
51+
workList.add(b);
52+
};
53+
54+
enqueue(targetBlock);
55+
enqueue(loop->getContinueBlock()); // Include continue block path if present
56+
57+
while (workList.getCount())
58+
{
59+
IRBlock* curr = workList.getLast();
60+
workList.removeLast();
61+
62+
if (curr == breakBlock)
63+
return true; // Found a path
64+
65+
IRInst* term = curr->getTerminator();
66+
if (!term)
67+
continue;
68+
69+
// If the terminator has *no* successor blocks (e.g. return, discard, unreachable, etc.)
70+
// then the loop can exit via this path.
71+
if (!as<IRConditionalBranch>(term) && !as<IRUnconditionalBranch>(term) &&
72+
!as<IRSwitch>(term))
73+
{
74+
return true;
75+
}
76+
77+
if (auto cb = as<IRConditionalBranch>(term))
78+
{
79+
enqueue(cb->getTrueBlock());
80+
enqueue(cb->getFalseBlock());
81+
}
82+
else if (auto ub = as<IRUnconditionalBranch>(term))
83+
{
84+
enqueue(ub->getTargetBlock());
85+
}
86+
else if (auto sw = as<IRSwitch>(term))
87+
{
88+
enqueue(sw->getDefaultLabel());
89+
for (UInt i = 0; i < sw->getCaseCount(); ++i)
90+
enqueue(sw->getCaseLabel(i));
91+
}
92+
// Other terminators (return, discard, unreachable, etc.) are exits from the loop body
93+
}
94+
95+
// If we exhaust reachable blocks without hitting the break block, assume the loop cannot exit
96+
return false;
97+
}
98+
99+
/// Get source location from the loop for diagnostics
100+
static SourceLoc getLoopSourceLocation(IRLoop* loop)
101+
{
102+
// Try to get source location from the loop itself
103+
if (loop->sourceLoc.isValid())
104+
return loop->sourceLoc;
105+
106+
// Try to get from the target block
107+
IRBlock* targetBlock = loop->getTargetBlock();
108+
if (targetBlock && targetBlock->sourceLoc.isValid())
109+
return targetBlock->sourceLoc;
110+
111+
// Try to get from instructions in the target block
112+
if (targetBlock)
113+
{
114+
for (auto inst : targetBlock->getOrdinaryInsts())
115+
{
116+
if (inst->sourceLoc.isValid())
117+
return inst->sourceLoc;
118+
}
119+
}
120+
121+
return SourceLoc();
122+
}
123+
124+
static void checkForPotentialInfiniteLoopsInInst(
125+
IRInst* inst,
126+
DiagnosticSink* sink,
127+
bool diagnoseWarning)
128+
{
129+
// Check if this instruction is a loop
130+
if (auto loop = as<IRLoop>(inst))
131+
{
132+
// Check if this loop has any path that can exit
133+
bool canExit = canLoopExit(loop);
134+
if (!canExit)
135+
{
136+
if (diagnoseWarning)
137+
{
138+
SourceLoc loc = getLoopSourceLocation(loop);
139+
sink->diagnose(loc, Diagnostics::potentialInfiniteLoop);
140+
}
141+
}
142+
}
143+
144+
// Recursively check children
145+
for (auto childInst : inst->getChildren())
146+
{
147+
checkForPotentialInfiniteLoopsInInst(childInst, sink, diagnoseWarning);
148+
}
149+
}
150+
151+
void checkForPotentialInfiniteLoops(IRModule* module, DiagnosticSink* sink, bool diagnoseWarning)
152+
{
153+
// Look for loops in the module
154+
checkForPotentialInfiniteLoopsInInst(module->getModuleInst(), sink, diagnoseWarning);
155+
}
156+
157+
} // namespace Slang
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// slang-ir-potential-infinite-loop.h
2+
#pragma once
3+
4+
namespace Slang
5+
{
6+
class DiagnosticSink;
7+
struct IRModule;
8+
struct IRInst;
9+
struct IRLoop;
10+
struct IRBlock;
11+
enum class CodeGenTarget;
12+
13+
/// This IR check pass analyzes loops in the IR to detect potential infinite loops.
14+
/// It looks for two key indicators of infinite loops:
15+
/// 1. Loops with constant conditions (always evaluating to true)
16+
/// 2. Loops with no exit paths (no branches to the break block)
17+
///
18+
/// When both conditions are met, it warns about a potential infinite loop.
19+
void checkForPotentialInfiniteLoops(IRModule* module, DiagnosticSink* sink, bool diagnoseWarning);
20+
} // namespace Slang

source/slang/slang-lower-to-ir.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "slang-ir-obfuscate-loc.h"
2727
#include "slang-ir-operator-shift-overflow.h"
2828
#include "slang-ir-peephole.h"
29+
#include "slang-ir-potential-infinite-loop.h"
2930
#include "slang-ir-sccp.h"
3031
#include "slang-ir-simplify-cfg.h"
3132
#include "slang-ir-ssa.h"
@@ -11944,6 +11945,10 @@ RefPtr<IRModule> generateIRForTranslationUnit(
1194411945
// instructions remain.
1194511946

1194611947
checkForMissingReturns(module, compileRequest->getSink(), CodeGenTarget::None, true);
11948+
11949+
// Check for potential infinite loops (loops with no break)
11950+
checkForPotentialInfiniteLoops(module, compileRequest->getSink(), true);
11951+
1194711952
// Check for invalid differentiable function body.
1194811953
checkAutoDiffUsages(module, compileRequest->getSink());
1194911954

tests/diagnostics/for-loop-warning.slang

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//DIAGNOSTIC_TEST:SIMPLE:
2-
2+
StructuredBuffer<float> buffer0;
3+
StructuredBuffer<float> buffer1;
4+
RWStructuredBuffer<float> result;
35

46
float doSomething(int x)
57
{
@@ -56,5 +58,9 @@ float doSomething(int x)
5658
{
5759
i--;
5860
}
61+
for (int i = 1; i < 10; i+1) // warn.
62+
{
63+
result[i] = buffer0[i] + buffer1[i];
64+
}
5965
return 0.0;
6066
}

tests/diagnostics/for-loop-warning.slang.expected

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ tests/diagnostics/for-loop-warning.slang(28): warning 30504: the for loop is sta
3030
tests/diagnostics/for-loop-warning.slang(32): warning 30504: the for loop is statically determined to terminate within 5 iterations, which is less than what [MaxIters] specifies.
3131
[MaxIters(6)] // warn
3232
^~~~~~~~
33+
tests/diagnostics/for-loop-warning.slang(10): warning 41008: loop might execute indefinitely
34+
for (int j = 0; j < 3; j += 0) // warn.
35+
^~~
3336
}
3437
standard output = {
3538
}

0 commit comments

Comments
 (0)