// | |
// Copyright (c) 2002-2015 The ANGLE Project Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style license that can be | |
// found in the LICENSE file. | |
// | |
#include "ValidateSwitch.h" | |
#include "ParseHelper.h" | |
bool ValidateSwitch::validate(TBasicType switchType, TParseContext *context, | |
TIntermAggregate *statementList, const TSourceLoc &loc) | |
{ | |
ValidateSwitch validate(switchType, context); | |
ASSERT(statementList); | |
statementList->traverse(&validate); | |
return validate.validateInternal(loc); | |
} | |
ValidateSwitch::ValidateSwitch(TBasicType switchType, TParseContext *context) | |
: TIntermTraverser(true, false, true), | |
mSwitchType(switchType), | |
mContext(context), | |
mCaseTypeMismatch(false), | |
mFirstCaseFound(false), | |
mStatementBeforeCase(false), | |
mLastStatementWasCase(false), | |
mControlFlowDepth(0), | |
mCaseInsideControlFlow(false), | |
mDefaultCount(0), | |
mDuplicateCases(false) | |
{} | |
void ValidateSwitch::visitSymbol(TIntermSymbol *) | |
{ | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
} | |
void ValidateSwitch::visitConstantUnion(TIntermConstantUnion *) | |
{ | |
// Conditions of case labels are not traversed, so this is some other constant | |
// Could be just a statement like "0;" | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
} | |
bool ValidateSwitch::visitBinary(Visit, TIntermBinary *) | |
{ | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
return true; | |
} | |
bool ValidateSwitch::visitUnary(Visit, TIntermUnary *) | |
{ | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
return true; | |
} | |
bool ValidateSwitch::visitSelection(Visit visit, TIntermSelection *) | |
{ | |
if (visit == PreVisit) | |
++mControlFlowDepth; | |
if (visit == PostVisit) | |
--mControlFlowDepth; | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
return true; | |
} | |
bool ValidateSwitch::visitSwitch(Visit, TIntermSwitch *) | |
{ | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
// Don't go into nested switch statements | |
return false; | |
} | |
bool ValidateSwitch::visitCase(Visit, TIntermCase *node) | |
{ | |
const char *nodeStr = node->hasCondition() ? "case" : "default"; | |
if (mControlFlowDepth > 0) | |
{ | |
mContext->error(node->getLine(), "label statement nested inside control flow", nodeStr); | |
mCaseInsideControlFlow = true; | |
} | |
mFirstCaseFound = true; | |
mLastStatementWasCase = true; | |
if (!node->hasCondition()) | |
{ | |
++mDefaultCount; | |
if (mDefaultCount > 1) | |
{ | |
mContext->error(node->getLine(), "duplicate default label", nodeStr); | |
} | |
} | |
else | |
{ | |
TIntermConstantUnion *condition = node->getCondition()->getAsConstantUnion(); | |
if (condition == nullptr) | |
{ | |
// This can happen in error cases. | |
return false; | |
} | |
TBasicType conditionType = condition->getBasicType(); | |
if (conditionType != mSwitchType) | |
{ | |
mContext->error(condition->getLine(), | |
"case label type does not match switch init-expression type", nodeStr); | |
mCaseTypeMismatch = true; | |
} | |
if (conditionType == EbtInt) | |
{ | |
int iConst = condition->getIConst(0); | |
if (mCasesSigned.find(iConst) != mCasesSigned.end()) | |
{ | |
mContext->error(condition->getLine(), "duplicate case label", nodeStr); | |
mDuplicateCases = true; | |
} | |
else | |
{ | |
mCasesSigned.insert(iConst); | |
} | |
} | |
else if (conditionType == EbtUInt) | |
{ | |
unsigned int uConst = condition->getUConst(0); | |
if (mCasesUnsigned.find(uConst) != mCasesUnsigned.end()) | |
{ | |
mContext->error(condition->getLine(), "duplicate case label", nodeStr); | |
mDuplicateCases = true; | |
} | |
else | |
{ | |
mCasesUnsigned.insert(uConst); | |
} | |
} | |
// Other types are possible only in error cases, where the error has already been generated | |
// when parsing the case statement. | |
} | |
// Don't traverse the condition of the case statement | |
return false; | |
} | |
bool ValidateSwitch::visitAggregate(Visit visit, TIntermAggregate *) | |
{ | |
if (getParentNode() != nullptr) | |
{ | |
// This is not the statementList node, but some other node. | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
} | |
return true; | |
} | |
bool ValidateSwitch::visitLoop(Visit visit, TIntermLoop *) | |
{ | |
if (visit == PreVisit) | |
++mControlFlowDepth; | |
if (visit == PostVisit) | |
--mControlFlowDepth; | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
return true; | |
} | |
bool ValidateSwitch::visitBranch(Visit, TIntermBranch *) | |
{ | |
if (!mFirstCaseFound) | |
mStatementBeforeCase = true; | |
mLastStatementWasCase = false; | |
return true; | |
} | |
bool ValidateSwitch::validateInternal(const TSourceLoc &loc) | |
{ | |
if (mStatementBeforeCase) | |
{ | |
mContext->error(loc, | |
"statement before the first label", "switch"); | |
} | |
if (mLastStatementWasCase) | |
{ | |
mContext->error(loc, | |
"no statement between the last label and the end of the switch statement", "switch"); | |
} | |
return !mStatementBeforeCase && !mLastStatementWasCase && !mCaseInsideControlFlow && | |
!mCaseTypeMismatch && mDefaultCount <= 1 && !mDuplicateCases; | |
} |