Migrate VerifyStaticTestsAndExpressions analysis pass out.

A followup CL will add new checks to this analysis pass (specifically,
error reporting for never-assigned out params). In preparation for this,
the code has been renamed to `DoFinalizationChecks` and migrated out of
SkSLAnalysis.cpp and into its own file in sksl/analysis/. No logic is
changing in this CL.

Change-Id: I7a474269be34350b3adfdd13fc1f384df143fde9
Bug: skia:12867
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/499755
Commit-Queue: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
John Stiles 2022-01-25 17:07:50 -05:00 committed by SkCQ
parent 9eb1679b77
commit 03e7350111
5 changed files with 119 additions and 90 deletions

View File

@ -83,6 +83,7 @@ skia_sksl_sources = [
"$_src/sksl/SkSLUtil.h",
"$_src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp",
"$_src/sksl/analysis/SkSLCheckProgramUnrolledSize.cpp",
"$_src/sksl/analysis/SkSLFinalizationChecks.cpp",
"$_src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp",
"$_src/sksl/analysis/SkSLIsConstantExpression.cpp",
"$_src/sksl/analysis/SkSLProgramUsage.cpp",

View File

@ -530,90 +530,6 @@ void Analysis::ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& e
visitor.visitProgramElement(pe);
}
void Analysis::VerifyStaticTestsAndExpressions(const Program& program) {
class TestsAndExpressions : public ProgramVisitor {
public:
TestsAndExpressions(const Context& ctx) : fContext(ctx) {}
bool visitProgramElement(const ProgramElement& pe) override {
if (pe.kind() == ProgramElement::Kind::kGlobalVar) {
const VarDeclaration& decl =
pe.as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>();
size_t prevSlotsUsed = fGlobalSlotsUsed;
fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var().type().slotCount());
// To avoid overzealous error reporting, only trigger the error at the first
// place where the global limit is exceeded.
if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) {
fContext.fErrors->error(pe.fLine, "global variable '" + decl.var().name() +
"' exceeds the size limit");
}
}
return INHERITED::visitProgramElement(pe);
}
bool visitStatement(const Statement& stmt) override {
if (!fContext.fConfig->fSettings.fPermitInvalidStaticTests) {
switch (stmt.kind()) {
case Statement::Kind::kIf:
if (stmt.as<IfStatement>().isStatic()) {
fContext.fErrors->error(stmt.fLine, "static if has non-static test");
}
break;
case Statement::Kind::kSwitch:
if (stmt.as<SwitchStatement>().isStatic()) {
fContext.fErrors->error(stmt.fLine,
"static switch has non-static test");
}
break;
default:
break;
}
}
return INHERITED::visitStatement(stmt);
}
bool visitExpression(const Expression& expr) override {
switch (expr.kind()) {
case Expression::Kind::kFunctionCall: {
const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
if (!decl.isBuiltin() && !decl.definition()) {
fContext.fErrors->error(expr.fLine, "function '" + decl.description() +
"' is not defined");
}
break;
}
case Expression::Kind::kExternalFunctionReference:
case Expression::Kind::kFunctionReference:
case Expression::Kind::kMethodReference:
case Expression::Kind::kTypeReference:
SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()");
fContext.fErrors->error(expr.fLine, "invalid expression");
break;
default:
if (expr.type().matches(*fContext.fTypes.fInvalid)) {
fContext.fErrors->error(expr.fLine, "invalid expression");
}
break;
}
return INHERITED::visitExpression(expr);
}
private:
using INHERITED = ProgramVisitor;
size_t fGlobalSlotsUsed = 0;
const Context& fContext;
};
// Check all of the program's owned elements. (Built-in elements are assumed to be valid.)
TestsAndExpressions visitor{*program.fContext};
for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
visitor.visitProgramElement(*element);
}
}
////////////////////////////////////////////////////////////////////////////////
// ProgramVisitor

View File

@ -172,10 +172,11 @@ void ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors);
bool CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl, const Statement& body);
/**
* Searches for @if/@switch statements that didn't optimize away, or dangling
* FunctionReference or TypeReference expressions, and reports them as errors.
* Runs at finalization time to perform any last-minute correctness checks:
* - Reports @if/@switch statements that didn't optimize away
* - Reports dangling FunctionReference or TypeReference expressions
*/
void VerifyStaticTestsAndExpressions(const Program& program);
void DoFinalizationChecks(const Program& program);
} // namespace Analysis
} // namespace SkSL

View File

@ -585,9 +585,10 @@ bool Compiler::runInliner(const std::vector<std::unique_ptr<ProgramElement>>& el
}
bool Compiler::finalize(Program& program) {
// Do a pass looking for @if/@switch statements that didn't optimize away, or dangling
// FunctionReference or TypeReference expressions. Report these as errors.
Analysis::VerifyStaticTestsAndExpressions(program);
// Do one last correctness-check pass. This looks for @if/@switch statements that didn't
// optimize away, or dangling FunctionReference or TypeReference expressions, and reports them
// as errors.
Analysis::DoFinalizationChecks(program);
// Verify that the program conforms to ES2 limitations.
if (fContext->fConfig->strictES2Mode() && this->errorCount() == 0) {

View File

@ -0,0 +1,110 @@
/*
* Copyright 2022 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/SkSLStatement.h"
#include "src/core/SkSafeMath.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/analysis/SkSLProgramVisitor.h"
#include "src/sksl/ir/SkSLFunctionCall.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLIfStatement.h"
#include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLSwitchStatement.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
namespace SkSL {
namespace {
class FinalizationVisitor : public ProgramVisitor {
public:
FinalizationVisitor(const Context& ctx) : fContext(ctx) {}
bool visitProgramElement(const ProgramElement& pe) override {
if (pe.kind() == ProgramElement::Kind::kGlobalVar) {
const VarDeclaration& decl =
pe.as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>();
size_t prevSlotsUsed = fGlobalSlotsUsed;
fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var().type().slotCount());
// To avoid overzealous error reporting, only trigger the error at the first
// place where the global limit is exceeded.
if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) {
fContext.fErrors->error(pe.fLine, "global variable '" + decl.var().name() +
"' exceeds the size limit");
}
}
return INHERITED::visitProgramElement(pe);
}
bool visitStatement(const Statement& stmt) override {
if (!fContext.fConfig->fSettings.fPermitInvalidStaticTests) {
switch (stmt.kind()) {
case Statement::Kind::kIf:
if (stmt.as<IfStatement>().isStatic()) {
fContext.fErrors->error(stmt.fLine, "static if has non-static test");
}
break;
case Statement::Kind::kSwitch:
if (stmt.as<SwitchStatement>().isStatic()) {
fContext.fErrors->error(stmt.fLine,
"static switch has non-static test");
}
break;
default:
break;
}
}
return INHERITED::visitStatement(stmt);
}
bool visitExpression(const Expression& expr) override {
switch (expr.kind()) {
case Expression::Kind::kFunctionCall: {
const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
if (!decl.isBuiltin() && !decl.definition()) {
fContext.fErrors->error(expr.fLine, "function '" + decl.description() +
"' is not defined");
}
break;
}
case Expression::Kind::kExternalFunctionReference:
case Expression::Kind::kFunctionReference:
case Expression::Kind::kMethodReference:
case Expression::Kind::kTypeReference:
SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()");
fContext.fErrors->error(expr.fLine, "invalid expression");
break;
default:
if (expr.type().matches(*fContext.fTypes.fInvalid)) {
fContext.fErrors->error(expr.fLine, "invalid expression");
}
break;
}
return INHERITED::visitExpression(expr);
}
private:
using INHERITED = ProgramVisitor;
size_t fGlobalSlotsUsed = 0;
const Context& fContext;
};
} // namespace
void Analysis::DoFinalizationChecks(const Program& program) {
// Check all of the program's owned elements. (Built-in elements are assumed to be valid.)
FinalizationVisitor visitor{*program.fContext};
for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
visitor.visitProgramElement(*element);
}
}
} // namespace SkSL