Clean up the Checker API a little more, resolving some hidden bugs
along the way. Important changes:
1) To generate a sink node, use GenerateSink(); GenerateNode() is for
generating regular transitions. This makes the API clearer and also
allows us to use the 'bool' option to GenerateNode() for a different
purpose.
2) GenerateNode() now automatically adds the generated node to the
destination ExplodedNodeSet (autotransition) unless the client
specifies otherwise with a bool flag. Several checkers did not call
'addTransition()' after calling 'GenerateNode()', causing the
simulation path to be prematurely culled when a non-fail stop bug was
encountered.
3) Add variants of GenerateNode()/GenerateSink() that take neither a
Stmt* or a GRState*; most callers of GenerateNode() just pass in the
same Stmt* as provided when the CheckerContext object is created; we
can just use that the majority of the time. This cleanup also allows
us to potentially coelesce the APIs for evaluating branches and
end-of-paths (which currently directly use builders).
4) addTransition() no longer needs to be called except for a few
cases. We now have a variant of addTransition() that takes a
GRState*; this allows one to propagate the updated state without
caring about generating a new node explicitly. This nicely cleaned up
a bunch of cases that called autoTransition() with a bunch of
conditional logic surround the call (that common logic has now been
swallowed up by addTransition() itself).
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@89707 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/Analysis/PathSensitive/Checker.h b/include/clang/Analysis/PathSensitive/Checker.h
index 1fb92a6..e94096b 100644
--- a/include/clang/Analysis/PathSensitive/Checker.h
+++ b/include/clang/Analysis/PathSensitive/Checker.h
@@ -40,24 +40,22 @@
SaveAndRestore<ProgramPoint::Kind> OldPointKind;
SaveOr OldHasGen;
const GRState *state;
-
+ const Stmt *statement;
+ const unsigned size;
public:
CheckerContext(ExplodedNodeSet &dst, GRStmtNodeBuilder &builder,
GRExprEngine &eng, ExplodedNode *pred,
const void *tag, ProgramPoint::Kind K,
- const GRState *st = 0)
+ const Stmt *stmt = 0, const GRState *st = 0)
: Dst(dst), B(builder), Eng(eng), Pred(pred),
OldSink(B.BuildSinks),
OldTag(B.Tag, tag),
OldPointKind(B.PointKind, K),
OldHasGen(B.HasGeneratedNode),
- state(st) {}
+ state(st), statement(stmt), size(Dst.size()) {}
- ~CheckerContext() {
- if (!B.BuildSinks && !B.HasGeneratedNode)
- Dst.Add(Pred);
- }
-
+ ~CheckerContext();
+
ConstraintManager &getConstraintManager() {
return Eng.getConstraintManager();
}
@@ -83,27 +81,66 @@
return getBugReporter().getSourceManager();
}
- ExplodedNode *GenerateNode(const Stmt *S, bool markAsSink = false) {
- return GenerateNode(S, getState(), markAsSink);
+ ExplodedNode *GenerateNode(bool autoTransition = true) {
+ assert(statement && "Only transitions with statements currently supported");
+ ExplodedNode *N = GenerateNodeImpl(statement, getState(), false);
+ if (N && autoTransition)
+ Dst.Add(N);
+ return N;
+ }
+
+ ExplodedNode *GenerateNode(const Stmt *stmt, const GRState *state,
+ bool autoTransition = true) {
+ assert(state);
+ ExplodedNode *N = GenerateNodeImpl(stmt, state, false);
+ if (N && autoTransition)
+ addTransition(N);
+ return N;
}
- ExplodedNode *GenerateNode(const Stmt* S, const GRState *state,
- bool markAsSink = false) {
- ExplodedNode *node = B.generateNode(S, state, Pred);
+ ExplodedNode *GenerateNode(const GRState *state, bool autoTransition = true) {
+ assert(statement && "Only transitions with statements currently supported");
+ ExplodedNode *N = GenerateNodeImpl(statement, state, false);
+ if (N && autoTransition)
+ addTransition(N);
+ return N;
+ }
- if (markAsSink && node)
- node->markAsSink();
-
- return node;
+ ExplodedNode *GenerateSink(const Stmt *stmt, const GRState *state = 0) {
+ return GenerateNodeImpl(stmt, state ? state : getState(), true);
+ }
+
+ ExplodedNode *GenerateSink(const GRState *state = 0) {
+ assert(statement && "Only transitions with statements currently supported");
+ return GenerateNodeImpl(statement, state ? state : getState(), true);
}
void addTransition(ExplodedNode *node) {
Dst.Add(node);
}
+
+ void addTransition(const GRState *state) {
+ assert(state);
+ if (state != getState() ||
+ (state && state != B.GetState(Pred)))
+ GenerateNode(state, true);
+ else
+ Dst.Add(Pred);
+ }
void EmitReport(BugReport *R) {
Eng.getBugReporter().EmitReport(R);
}
+
+private:
+ ExplodedNode *GenerateNodeImpl(const Stmt* stmt, const GRState *state,
+ bool markAsSink) {
+ ExplodedNode *node = B.generateNode(stmt, state, Pred);
+ if (markAsSink && node)
+ node->markAsSink();
+ return node;
+ }
+
};
class Checker {
@@ -118,7 +155,7 @@
ExplodedNode *Pred, void *tag, bool isPrevisit) {
CheckerContext C(Dst, Builder, Eng, Pred, tag,
isPrevisit ? ProgramPoint::PreStmtKind :
- ProgramPoint::PostStmtKind);
+ ProgramPoint::PostStmtKind, S);
if (isPrevisit)
_PreVisit(C, S);
else
@@ -134,7 +171,7 @@
bool isPrevisit) {
CheckerContext C(Dst, Builder, Eng, Pred, tag,
isPrevisit ? ProgramPoint::PreStmtKind :
- ProgramPoint::PostStmtKind);
+ ProgramPoint::PostStmtKind, StoreE);
assert(isPrevisit && "Only previsit supported for now.");
PreVisitBind(C, AssignE, StoreE, location, val);
}
@@ -149,7 +186,7 @@
void *tag, bool isLoad) {
CheckerContext C(Dst, Builder, Eng, Pred, tag,
isLoad ? ProgramPoint::PreLoadKind :
- ProgramPoint::PreStoreKind, state);
+ ProgramPoint::PreStoreKind, S, state);
VisitLocation(C, S, location);
}
@@ -157,7 +194,7 @@
GRExprEngine &Eng, const Stmt *S, ExplodedNode *Pred,
SymbolReaper &SymReaper, void *tag) {
CheckerContext C(Dst, Builder, Eng, Pred, tag,
- ProgramPoint::PostPurgeDeadSymbolsKind);
+ ProgramPoint::PostPurgeDeadSymbolsKind, S);
EvalDeadSymbols(C, S, SymReaper);
}
diff --git a/lib/Analysis/ArrayBoundChecker.cpp b/lib/Analysis/ArrayBoundChecker.cpp
index 549a22b..3d8b3b3 100644
--- a/lib/Analysis/ArrayBoundChecker.cpp
+++ b/lib/Analysis/ArrayBoundChecker.cpp
@@ -62,8 +62,7 @@
const GRState *StInBound = state->AssumeInBound(Idx, NumElements, true);
const GRState *StOutBound = state->AssumeInBound(Idx, NumElements, false);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.GenerateNode(S, StOutBound, true);
-
+ ExplodedNode *N = C.GenerateSink(StOutBound);
if (!N)
return;
@@ -80,7 +79,6 @@
new RangedBugReport(*BT, BT->getDescription(), N);
report->addRange(S->getSourceRange());
-
C.EmitReport(report);
}
}
diff --git a/lib/Analysis/AttrNonNullChecker.cpp b/lib/Analysis/AttrNonNullChecker.cpp
index 01e1a1f..8668c75 100644
--- a/lib/Analysis/AttrNonNullChecker.cpp
+++ b/lib/Analysis/AttrNonNullChecker.cpp
@@ -39,7 +39,6 @@
void AttrNonNullChecker::PreVisitCallExpr(CheckerContext &C,
const CallExpr *CE) {
const GRState *state = C.getState();
- const GRState *originalState = state;
// Check if the callee has a 'nonnull' attribute.
SVal X = state->getSVal(CE->getCallee());
@@ -74,7 +73,7 @@
if (stateNull && !stateNotNull) {
// Generate an error node. Check for a null node in case
// we cache out.
- if (ExplodedNode *errorNode = C.GenerateNode(CE, stateNull, true)) {
+ if (ExplodedNode *errorNode = C.GenerateSink(stateNull)) {
// Lazily allocate the BugType object if it hasn't already been
// created. Ownership is transferred to the BugReporter object once
@@ -109,6 +108,5 @@
// If we reach here all of the arguments passed the nonnull check.
// If 'state' has been updated generated a new node.
- if (state != originalState)
- C.addTransition(C.GenerateNode(CE, state));
+ C.addTransition(state);
}
diff --git a/lib/Analysis/BasicObjCFoundationChecks.cpp b/lib/Analysis/BasicObjCFoundationChecks.cpp
index 5a88a7a..424a0b3 100644
--- a/lib/Analysis/BasicObjCFoundationChecks.cpp
+++ b/lib/Analysis/BasicObjCFoundationChecks.cpp
@@ -563,12 +563,11 @@
BT = new APIMisuse("message incorrectly sent to class instead of class "
"instance");
- ExplodedNode *N = C.GenerateNode(ME, C.getState(), false);
+ ExplodedNode *N = C.GenerateNode();
+
if (!N)
return;
- C.addTransition(N);
-
llvm::SmallString<200> buf;
llvm::raw_svector_ostream os(buf);
diff --git a/lib/Analysis/CastToStructChecker.cpp b/lib/Analysis/CastToStructChecker.cpp
index ccd4a33..7c6fc7e 100644
--- a/lib/Analysis/CastToStructChecker.cpp
+++ b/lib/Analysis/CastToStructChecker.cpp
@@ -59,7 +59,7 @@
// Now the cast-to-type is struct pointer, the original type is not void*.
if (!OrigPointeeTy->isRecordType()) {
- if (ExplodedNode *N = C.GenerateNode(CE)) {
+ if (ExplodedNode *N = C.GenerateNode()) {
if (!BT)
BT = new BuiltinBug("Cast from non-struct type to struct type",
"Casting a non-structure type to a structure type "
diff --git a/lib/Analysis/Checker.cpp b/lib/Analysis/Checker.cpp
index 985b1e0..0d907e5 100644
--- a/lib/Analysis/Checker.cpp
+++ b/lib/Analysis/Checker.cpp
@@ -16,3 +16,20 @@
using namespace clang;
Checker::~Checker() {}
+
+CheckerContext::~CheckerContext() {
+ // Do we need to autotransition? 'Dst' can get populated in a variety of
+ // ways, including 'addTransition()' adding the predecessor node to Dst
+ // without actually generated a new node. We also shouldn't autotransition
+ // if we are building sinks or we generated a node and decided to not
+ // add it as a transition.
+ if (Dst.size() == size && !B.BuildSinks && !B.HasGeneratedNode) {
+ if (state && state != B.GetState(Pred)) {
+ static int autoTransitionTag = 0;
+ B.Tag = &autoTransitionTag;
+ addTransition(state);
+ }
+ else
+ Dst.Add(Pred);
+ }
+}
diff --git a/lib/Analysis/DereferenceChecker.cpp b/lib/Analysis/DereferenceChecker.cpp
index a8f5af3..4c4091c 100644
--- a/lib/Analysis/DereferenceChecker.cpp
+++ b/lib/Analysis/DereferenceChecker.cpp
@@ -56,8 +56,7 @@
SVal l) {
// Check for dereference of an undefined value.
if (l.isUndef()) {
- ExplodedNode *N = C.GenerateNode(S, true);
- if (N) {
+ if (ExplodedNode *N = C.GenerateSink()) {
if (!BT_undef)
BT_undef = new BuiltinBug("Dereference of undefined pointer value");
@@ -84,7 +83,7 @@
if (nullState) {
if (!notNullState) {
// Generate an error node.
- ExplodedNode *N = C.GenerateNode(S, nullState, true);
+ ExplodedNode *N = C.GenerateSink(nullState);
if (!N)
return;
@@ -106,13 +105,11 @@
// Otherwise, we have the case where the location could either be
// null or not-null. Record the error node as an "implicit" null
// dereference.
- if (ExplodedNode *N = C.GenerateNode(S, nullState, true))
+ if (ExplodedNode *N = C.GenerateSink(nullState))
ImplicitNullDerefNodes.push_back(N);
}
}
// From this point forward, we know that the location is not null.
- assert(notNullState);
- C.addTransition(state != nullState ? C.GenerateNode(S, notNullState) :
- C.getPredecessor());
+ C.addTransition(notNullState);
}
diff --git a/lib/Analysis/DivZeroChecker.cpp b/lib/Analysis/DivZeroChecker.cpp
index a8630f1..4052637 100644
--- a/lib/Analysis/DivZeroChecker.cpp
+++ b/lib/Analysis/DivZeroChecker.cpp
@@ -63,7 +63,7 @@
llvm::tie(stateNotZero, stateZero) = CM.AssumeDual(C.getState(), *DV);
if (stateZero && !stateNotZero) {
- if (ExplodedNode *N = C.GenerateNode(B, stateZero, true)) {
+ if (ExplodedNode *N = C.GenerateSink(stateZero)) {
if (!BT)
BT = new BuiltinBug("Division by zero");
@@ -80,6 +80,5 @@
// If we get here, then the denom should not be zero. We abandon the implicit
// zero denom case for now.
- if (stateNotZero != C.getState())
- C.addTransition(C.GenerateNode(B, stateNotZero));
+ C.addTransition(stateNotZero);
}
diff --git a/lib/Analysis/FixedAddressChecker.cpp b/lib/Analysis/FixedAddressChecker.cpp
index 80096dc..d8adaaf 100644
--- a/lib/Analysis/FixedAddressChecker.cpp
+++ b/lib/Analysis/FixedAddressChecker.cpp
@@ -53,7 +53,7 @@
if (!RV.isConstant() || RV.isZeroConstant())
return;
- if (ExplodedNode *N = C.GenerateNode(B)) {
+ if (ExplodedNode *N = C.GenerateNode()) {
if (!BT)
BT = new BuiltinBug("Use fixed address",
"Using a fixed address is not portable because that "
diff --git a/lib/Analysis/GRExprEngine.cpp b/lib/Analysis/GRExprEngine.cpp
index 4f3440d..f657aeb 100644
--- a/lib/Analysis/GRExprEngine.cpp
+++ b/lib/Analysis/GRExprEngine.cpp
@@ -1292,9 +1292,13 @@
Checker *checker = I->second;
for (ExplodedNodeSet::iterator NI = PrevSet->begin(), NE = PrevSet->end();
- NI != NE; ++NI)
- checker->GR_VisitLocation(*CurrSet, *Builder, *this, S, *NI, state,
+ NI != NE; ++NI) {
+ // Use the 'state' argument only when the predecessor node is the
+ // same as Pred. This allows us to catch updates to the state.
+ checker->GR_VisitLocation(*CurrSet, *Builder, *this, S, *NI,
+ *NI == Pred ? state : GetState(*NI),
location, tag, isLoad);
+ }
// Update which NodeSet is the current one.
PrevSet = CurrSet;
diff --git a/lib/Analysis/MallocChecker.cpp b/lib/Analysis/MallocChecker.cpp
index 995720b..a16125d 100644
--- a/lib/Analysis/MallocChecker.cpp
+++ b/lib/Analysis/MallocChecker.cpp
@@ -112,9 +112,7 @@
SymbolRef Sym = CallVal.getAsLocSymbol();
assert(Sym);
// Set the symbol's state to Allocated.
- const GRState *AllocState
- = state->set<RegionState>(Sym, RefState::getAllocated(CE));
- C.addTransition(C.GenerateNode(CE, AllocState));
+ C.addTransition(state->set<RegionState>(Sym, RefState::getAllocated(CE)));
}
void MallocChecker::FreeMem(CheckerContext &C, const CallExpr *CE) {
@@ -128,7 +126,7 @@
// Check double free.
if (RS->isReleased()) {
- ExplodedNode *N = C.GenerateNode(CE, true);
+ ExplodedNode *N = C.GenerateSink();
if (N) {
if (!BT_DoubleFree)
BT_DoubleFree = new BuiltinBug("Double free",
@@ -144,7 +142,7 @@
// Normal free.
const GRState *FreedState
= state->set<RegionState>(Sym, RefState::getReleased(CE));
- C.addTransition(C.GenerateNode(CE, FreedState));
+ C.addTransition(FreedState);
}
void MallocChecker::EvalDeadSymbols(CheckerContext &C, const Stmt *S,
@@ -158,7 +156,7 @@
return;
if (RS->isAllocated()) {
- ExplodedNode *N = C.GenerateNode(S, true);
+ ExplodedNode *N = C.GenerateSink();
if (N) {
if (!BT_Leak)
BT_Leak = new BuiltinBug("Memory leak",
@@ -213,7 +211,5 @@
if (RS->isAllocated())
state = state->set<RegionState>(Sym, RefState::getEscaped(S));
- ExplodedNode *N = C.GenerateNode(S, state);
- if (N)
- C.addTransition(N);
+ C.addTransition(state);
}
diff --git a/lib/Analysis/PointerArithChecker.cpp b/lib/Analysis/PointerArithChecker.cpp
index 9382348..6bf1a3f 100644
--- a/lib/Analysis/PointerArithChecker.cpp
+++ b/lib/Analysis/PointerArithChecker.cpp
@@ -53,7 +53,7 @@
if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) ||
isa<CompoundLiteralRegion>(LR)) {
- if (ExplodedNode *N = C.GenerateNode(B)) {
+ if (ExplodedNode *N = C.GenerateNode()) {
if (!BT)
BT = new BuiltinBug("Dangerous pointer arithmetic",
"Pointer arithmetic done on non-array variables "
diff --git a/lib/Analysis/PointerSubChecker.cpp b/lib/Analysis/PointerSubChecker.cpp
index 4c7906f..50f5025 100644
--- a/lib/Analysis/PointerSubChecker.cpp
+++ b/lib/Analysis/PointerSubChecker.cpp
@@ -61,7 +61,7 @@
if (isa<SymbolicRegion>(BaseLR) || isa<SymbolicRegion>(BaseRR))
return;
- if (ExplodedNode *N = C.GenerateNode(B)) {
+ if (ExplodedNode *N = C.GenerateNode()) {
if (!BT)
BT = new BuiltinBug("Pointer subtraction",
"Subtraction of two pointers that do not point to "
diff --git a/lib/Analysis/ReturnPointerRangeChecker.cpp b/lib/Analysis/ReturnPointerRangeChecker.cpp
index 44887b2..8a19294 100644
--- a/lib/Analysis/ReturnPointerRangeChecker.cpp
+++ b/lib/Analysis/ReturnPointerRangeChecker.cpp
@@ -70,7 +70,7 @@
const GRState *StInBound = state->AssumeInBound(Idx, NumElements, true);
const GRState *StOutBound = state->AssumeInBound(Idx, NumElements, false);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.GenerateNode(RS, StOutBound, true);
+ ExplodedNode *N = C.GenerateSink(StOutBound);
if (!N)
return;
@@ -91,7 +91,6 @@
new RangedBugReport(*BT, BT->getDescription(), N);
report->addRange(RetE->getSourceRange());
-
C.EmitReport(report);
}
}
diff --git a/lib/Analysis/ReturnStackAddressChecker.cpp b/lib/Analysis/ReturnStackAddressChecker.cpp
index e4be871..e8a014a 100644
--- a/lib/Analysis/ReturnStackAddressChecker.cpp
+++ b/lib/Analysis/ReturnStackAddressChecker.cpp
@@ -53,7 +53,7 @@
if (!R || !R->hasStackStorage())
return;
- ExplodedNode *N = C.GenerateNode(RS, C.getState(), true);
+ ExplodedNode *N = C.GenerateSink();
if (!N)
return;
diff --git a/lib/Analysis/ReturnUndefChecker.cpp b/lib/Analysis/ReturnUndefChecker.cpp
index 796c760..48163b4 100644
--- a/lib/Analysis/ReturnUndefChecker.cpp
+++ b/lib/Analysis/ReturnUndefChecker.cpp
@@ -50,7 +50,7 @@
if (!C.getState()->getSVal(RetE).isUndef())
return;
- ExplodedNode *N = C.GenerateNode(RS, C.getState(), true);
+ ExplodedNode *N = C.GenerateSink();
if (!N)
return;
diff --git a/lib/Analysis/UndefinedArgChecker.cpp b/lib/Analysis/UndefinedArgChecker.cpp
index ea7d971..e717f6b 100644
--- a/lib/Analysis/UndefinedArgChecker.cpp
+++ b/lib/Analysis/UndefinedArgChecker.cpp
@@ -47,7 +47,7 @@
void UndefinedArgChecker::EmitBadCall(BugType *BT, CheckerContext &C,
const CallExpr *CE) {
- ExplodedNode *N = C.GenerateNode(CE, true);
+ ExplodedNode *N = C.GenerateSink();
if (!N)
return;
@@ -81,7 +81,7 @@
for (CallExpr::const_arg_iterator I = CE->arg_begin(), E = CE->arg_end();
I != E; ++I) {
if (C.getState()->getSVal(*I).isUndef()) {
- if (ExplodedNode *N = C.GenerateNode(CE, true)) {
+ if (ExplodedNode *N = C.GenerateSink()) {
if (!BT_call_arg)
BT_call_arg = new BuiltinBug("Pass-by-value argument in function call"
" is undefined");
@@ -104,7 +104,7 @@
if (const Expr *receiver = ME->getReceiver())
if (state->getSVal(receiver).isUndef()) {
- if (ExplodedNode *N = C.GenerateNode(ME, true)) {
+ if (ExplodedNode *N = C.GenerateSink()) {
if (!BT_msg_undef)
BT_msg_undef =
new BuiltinBug("Receiver in message expression is a garbage value");
@@ -122,7 +122,7 @@
for (ObjCMessageExpr::const_arg_iterator I = ME->arg_begin(), E = ME->arg_end();
I != E; ++I) {
if (state->getSVal(*I).isUndef()) {
- if (ExplodedNode *N = C.GenerateNode(ME, true)) {
+ if (ExplodedNode *N = C.GenerateSink()) {
if (!BT_msg_arg)
BT_msg_arg =
new BuiltinBug("Pass-by-value argument in message expression"
diff --git a/lib/Analysis/UndefinedArraySubscriptChecker.cpp b/lib/Analysis/UndefinedArraySubscriptChecker.cpp
index 887c775..3ae0c57 100644
--- a/lib/Analysis/UndefinedArraySubscriptChecker.cpp
+++ b/lib/Analysis/UndefinedArraySubscriptChecker.cpp
@@ -41,7 +41,7 @@
UndefinedArraySubscriptChecker::PreVisitArraySubscriptExpr(CheckerContext &C,
const ArraySubscriptExpr *A) {
if (C.getState()->getSVal(A->getIdx()).isUndef()) {
- if (ExplodedNode *N = C.GenerateNode(A, true)) {
+ if (ExplodedNode *N = C.GenerateSink()) {
if (!BT)
BT = new BuiltinBug("Array subscript is undefined");
diff --git a/lib/Analysis/UndefinedAssignmentChecker.cpp b/lib/Analysis/UndefinedAssignmentChecker.cpp
index 0e911ff..1c5b25c 100644
--- a/lib/Analysis/UndefinedAssignmentChecker.cpp
+++ b/lib/Analysis/UndefinedAssignmentChecker.cpp
@@ -48,7 +48,7 @@
if (!val.isUndef())
return;
- ExplodedNode *N = C.GenerateNode(StoreE, true);
+ ExplodedNode *N = C.GenerateSink();
if (!N)
return;
diff --git a/lib/Analysis/VLASizeChecker.cpp b/lib/Analysis/VLASizeChecker.cpp
index 799a73e..9a3436c 100644
--- a/lib/Analysis/VLASizeChecker.cpp
+++ b/lib/Analysis/VLASizeChecker.cpp
@@ -55,7 +55,7 @@
if (sizeV.isUndef()) {
// Generate an error node.
- ExplodedNode *N = C.GenerateNode(DS, true);
+ ExplodedNode *N = C.GenerateSink();
if (!N)
return;
@@ -78,7 +78,7 @@
llvm::tie(stateNotZero, stateZero) = state->Assume(sizeD);
if (stateZero && !stateNotZero) {
- ExplodedNode* N = C.GenerateNode(DS, stateZero, true);
+ ExplodedNode* N = C.GenerateSink(stateZero);
if (!BT_zero)
BT_zero = new BuiltinBug("Declared variable-length array (VLA) has zero "
"size");
@@ -92,6 +92,5 @@
}
// From this point on, assume that the size is not zero.
- if (state != stateNotZero)
- C.addTransition(C.GenerateNode(DS, stateNotZero));
+ C.addTransition(stateNotZero);
}