blob: 8a9e7b22a1d74a7912a3496353e71c75cbe01997 [file] [log] [blame]
//
// Copyright 2002 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 "compiler/translator/ValidateSwitch.h"
#include "compiler/translator/Diagnostics.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
const int kMaxAllowedTraversalDepth = 256;
class ValidateSwitch : public TIntermTraverser
{
public:
static bool validate(TBasicType switchType,
TDiagnostics *diagnostics,
TIntermBlock *statementList,
const TSourceLoc &loc);
void visitSymbol(TIntermSymbol *) override;
void visitConstantUnion(TIntermConstantUnion *) override;
bool visitDeclaration(Visit, TIntermDeclaration *) override;
bool visitBlock(Visit visit, TIntermBlock *) override;
bool visitBinary(Visit, TIntermBinary *) override;
bool visitUnary(Visit, TIntermUnary *) override;
bool visitTernary(Visit, TIntermTernary *) override;
bool visitSwizzle(Visit, TIntermSwizzle *) override;
bool visitIfElse(Visit visit, TIntermIfElse *) override;
bool visitSwitch(Visit, TIntermSwitch *) override;
bool visitCase(Visit, TIntermCase *node) override;
bool visitAggregate(Visit, TIntermAggregate *) override;
bool visitLoop(Visit visit, TIntermLoop *) override;
bool visitBranch(Visit, TIntermBranch *) override;
private:
ValidateSwitch(TBasicType switchType, TDiagnostics *context);
bool validateInternal(const TSourceLoc &loc);
TBasicType mSwitchType;
TDiagnostics *mDiagnostics;
bool mCaseTypeMismatch;
bool mFirstCaseFound;
bool mStatementBeforeCase;
bool mLastStatementWasCase;
int mControlFlowDepth;
bool mCaseInsideControlFlow;
int mDefaultCount;
std::set<int> mCasesSigned;
std::set<unsigned int> mCasesUnsigned;
bool mDuplicateCases;
};
bool ValidateSwitch::validate(TBasicType switchType,
TDiagnostics *diagnostics,
TIntermBlock *statementList,
const TSourceLoc &loc)
{
ValidateSwitch validate(switchType, diagnostics);
ASSERT(statementList);
statementList->traverse(&validate);
return validate.validateInternal(loc);
}
ValidateSwitch::ValidateSwitch(TBasicType switchType, TDiagnostics *diagnostics)
: TIntermTraverser(true, false, true, nullptr),
mSwitchType(switchType),
mDiagnostics(diagnostics),
mCaseTypeMismatch(false),
mFirstCaseFound(false),
mStatementBeforeCase(false),
mLastStatementWasCase(false),
mControlFlowDepth(0),
mCaseInsideControlFlow(false),
mDefaultCount(0),
mDuplicateCases(false)
{
setMaxAllowedDepth(kMaxAllowedTraversalDepth);
}
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::visitDeclaration(Visit, TIntermDeclaration *)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
return true;
}
bool ValidateSwitch::visitBlock(Visit visit, TIntermBlock *)
{
if (getParentNode() != nullptr)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
if (visit == PreVisit)
++mControlFlowDepth;
if (visit == PostVisit)
--mControlFlowDepth;
}
return true;
}
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::visitTernary(Visit, TIntermTernary *)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
return true;
}
bool ValidateSwitch::visitSwizzle(Visit, TIntermSwizzle *)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
return true;
}
bool ValidateSwitch::visitIfElse(Visit visit, TIntermIfElse *)
{
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)
{
mDiagnostics->error(node->getLine(), "label statement nested inside control flow", nodeStr);
mCaseInsideControlFlow = true;
}
mFirstCaseFound = true;
mLastStatementWasCase = true;
if (!node->hasCondition())
{
++mDefaultCount;
if (mDefaultCount > 1)
{
mDiagnostics->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)
{
mDiagnostics->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())
{
mDiagnostics->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())
{
mDiagnostics->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)
{
mDiagnostics->error(loc, "statement before the first label", "switch");
}
if (mLastStatementWasCase)
{
// There have been some differences between versions of GLSL ES specs on whether this should
// be an error or not, but as of early 2018 the latest discussion is that this is an error
// also on GLSL ES versions newer than 3.00.
mDiagnostics->error(
loc, "no statement between the last label and the end of the switch statement",
"switch");
}
if (getMaxDepth() >= kMaxAllowedTraversalDepth)
{
mDiagnostics->error(loc, "too complex expressions inside a switch statement", "switch");
}
return !mStatementBeforeCase && !mLastStatementWasCase && !mCaseInsideControlFlow &&
!mCaseTypeMismatch && mDefaultCount <= 1 && !mDuplicateCases &&
getMaxDepth() < kMaxAllowedTraversalDepth;
}
} // anonymous namespace
bool ValidateSwitchStatementList(TBasicType switchType,
TDiagnostics *diagnostics,
TIntermBlock *statementList,
const TSourceLoc &loc)
{
return ValidateSwitch::validate(switchType, diagnostics, statementList, loc);
}
} // namespace sh