blob: a5b6e1132f1ecd9a3379d88cc2e746225f963de8 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/private/SkSLDefines.h"
#include "include/private/SkSLIRNode.h"
#include "include/private/SkSLStatement.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/analysis/SkSLProgramVisitor.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include <algorithm>
#include <memory>
namespace SkSL {
class Expression;
static int count_returns_at_end_of_control_flow(const FunctionDefinition& funcDef) {
class CountReturnsAtEndOfControlFlow : public ProgramVisitor {
public:
CountReturnsAtEndOfControlFlow(const FunctionDefinition& funcDef) {
this->visitProgramElement(funcDef);
}
bool visitExpression(const Expression& expr) override {
// Do not recurse into expressions.
return false;
}
bool visitStatement(const Statement& stmt) override {
switch (stmt.kind()) {
case Statement::Kind::kBlock: {
// Check only the last statement of a block.
const auto& block = stmt.as<Block>();
return block.children().size() &&
this->visitStatement(*block.children().back());
}
case Statement::Kind::kSwitch:
case Statement::Kind::kDo:
case Statement::Kind::kFor:
// Don't introspect switches or loop structures at all.
return false;
case Statement::Kind::kReturn:
++fNumReturns;
[[fallthrough]];
default:
return INHERITED::visitStatement(stmt);
}
}
int fNumReturns = 0;
using INHERITED = ProgramVisitor;
};
return CountReturnsAtEndOfControlFlow{funcDef}.fNumReturns;
}
class CountReturnsWithLimit : public ProgramVisitor {
public:
CountReturnsWithLimit(const FunctionDefinition& funcDef, int limit) : fLimit(limit) {
this->visitProgramElement(funcDef);
}
bool visitExpression(const Expression& expr) override {
// Do not recurse into expressions.
return false;
}
bool visitStatement(const Statement& stmt) override {
switch (stmt.kind()) {
case Statement::Kind::kReturn: {
++fNumReturns;
fDeepestReturn = std::max(fDeepestReturn, fScopedBlockDepth);
return (fNumReturns >= fLimit) || INHERITED::visitStatement(stmt);
}
case Statement::Kind::kVarDeclaration: {
if (fScopedBlockDepth > 1) {
fVariablesInBlocks = true;
}
return INHERITED::visitStatement(stmt);
}
case Statement::Kind::kBlock: {
int depthIncrement = stmt.as<Block>().isScope() ? 1 : 0;
fScopedBlockDepth += depthIncrement;
bool result = INHERITED::visitStatement(stmt);
fScopedBlockDepth -= depthIncrement;
if (fNumReturns == 0 && fScopedBlockDepth <= 1) {
// If closing this block puts us back at the top level, and we haven't
// encountered any return statements yet, any vardecls we may have encountered
// up until this point can be ignored. They are out of scope now, and they were
// never used in a return statement.
fVariablesInBlocks = false;
}
return result;
}
default:
return INHERITED::visitStatement(stmt);
}
}
int fNumReturns = 0;
int fDeepestReturn = 0;
int fLimit = 0;
int fScopedBlockDepth = 0;
bool fVariablesInBlocks = false;
using INHERITED = ProgramVisitor;
};
Analysis::ReturnComplexity Analysis::GetReturnComplexity(const FunctionDefinition& funcDef) {
int returnsAtEndOfControlFlow = count_returns_at_end_of_control_flow(funcDef);
CountReturnsWithLimit counter{funcDef, returnsAtEndOfControlFlow + 1};
if (counter.fNumReturns > returnsAtEndOfControlFlow) {
return ReturnComplexity::kEarlyReturns;
}
if (counter.fNumReturns > 1) {
return ReturnComplexity::kScopedReturns;
}
if (counter.fVariablesInBlocks && counter.fDeepestReturn > 1) {
return ReturnComplexity::kScopedReturns;
}
return ReturnComplexity::kSingleSafeReturn;
}
} // namespace SkSL