1 /*
2 * Copyright 2022 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/private/SkSLStatement.h"
9 #include "src/core/SkSafeMath.h"
10 #include "src/sksl/SkSLAnalysis.h"
11 #include "src/sksl/SkSLContext.h"
12 #include "src/sksl/SkSLProgramSettings.h"
13 #include "src/sksl/analysis/SkSLProgramVisitor.h"
14 #include "src/sksl/ir/SkSLFunctionCall.h"
15 #include "src/sksl/ir/SkSLFunctionDeclaration.h"
16 #include "src/sksl/ir/SkSLFunctionDefinition.h"
17 #include "src/sksl/ir/SkSLIfStatement.h"
18 #include "src/sksl/ir/SkSLProgram.h"
19 #include "src/sksl/ir/SkSLSwitchStatement.h"
20 #include "src/sksl/ir/SkSLVarDeclarations.h"
21 #include "src/sksl/ir/SkSLVariable.h"
22
23 namespace SkSL {
24 namespace {
25
26 class FinalizationVisitor : public ProgramVisitor {
27 public:
FinalizationVisitor(const Context & c,const ProgramUsage & u)28 FinalizationVisitor(const Context& c, const ProgramUsage& u) : fContext(c), fUsage(u) {}
29
visitProgramElement(const ProgramElement & pe)30 bool visitProgramElement(const ProgramElement& pe) override {
31 switch (pe.kind()) {
32 case ProgramElement::Kind::kGlobalVar: {
33 this->checkGlobalVariableSizeLimit(pe.as<GlobalVarDeclaration>());
34 break;
35 }
36 case ProgramElement::Kind::kFunction: {
37 this->checkOutParamsAreAssigned(pe.as<FunctionDefinition>());
38 break;
39 }
40 default:
41 break;
42 }
43 return INHERITED::visitProgramElement(pe);
44 }
45
checkGlobalVariableSizeLimit(const GlobalVarDeclaration & globalDecl)46 void checkGlobalVariableSizeLimit(const GlobalVarDeclaration& globalDecl) {
47 const VarDeclaration& decl = globalDecl.declaration()->as<VarDeclaration>();
48
49 size_t prevSlotsUsed = fGlobalSlotsUsed;
50 fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var().type().slotCount());
51 // To avoid overzealous error reporting, only trigger the error at the first place where the
52 // global limit is exceeded.
53 if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) {
54 fContext.fErrors->error(decl.fLine,
55 "global variable '" + std::string(decl.var().name()) +
56 "' exceeds the size limit");
57 }
58 }
59
checkOutParamsAreAssigned(const FunctionDefinition & funcDef)60 void checkOutParamsAreAssigned(const FunctionDefinition& funcDef) {
61 const FunctionDeclaration& funcDecl = funcDef.declaration();
62
63 // Searches for `out` parameters that are not written to. According to the GLSL spec,
64 // the value of an out-param that's never assigned to is unspecified, so report it.
65 // Structs are currently exempt from the rule because custom mesh specifications require an
66 // `out` parameter for a Varyings struct, even if your mesh program doesn't need Varyings.
67 for (const Variable* param : funcDecl.parameters()) {
68 const int paramInout = param->modifiers().fFlags & (Modifiers::Flag::kIn_Flag |
69 Modifiers::Flag::kOut_Flag);
70 if (!param->type().isStruct() && paramInout == Modifiers::Flag::kOut_Flag) {
71 ProgramUsage::VariableCounts counts = fUsage.get(*param);
72 if (counts.fWrite <= 0) {
73 fContext.fErrors->error(funcDecl.fLine,
74 "function '" + std::string(funcDecl.name()) +
75 "' never assigns a value to out parameter '" +
76 std::string(param->name()) + "'");
77 }
78 }
79 }
80 }
81
visitStatement(const Statement & stmt)82 bool visitStatement(const Statement& stmt) override {
83 if (!fContext.fConfig->fSettings.fPermitInvalidStaticTests) {
84 switch (stmt.kind()) {
85 case Statement::Kind::kIf:
86 if (stmt.as<IfStatement>().isStatic()) {
87 fContext.fErrors->error(stmt.fLine, "static if has non-static test");
88 }
89 break;
90
91 case Statement::Kind::kSwitch:
92 if (stmt.as<SwitchStatement>().isStatic()) {
93 fContext.fErrors->error(stmt.fLine, "static switch has non-static test");
94 }
95 break;
96
97 default:
98 break;
99 }
100 }
101 return INHERITED::visitStatement(stmt);
102 }
103
visitExpression(const Expression & expr)104 bool visitExpression(const Expression& expr) override {
105 switch (expr.kind()) {
106 case Expression::Kind::kFunctionCall: {
107 const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
108 if (!decl.isBuiltin() && !decl.definition()) {
109 fContext.fErrors->error(expr.fLine, "function '" + decl.description() +
110 "' is not defined");
111 }
112 break;
113 }
114 case Expression::Kind::kExternalFunctionReference:
115 case Expression::Kind::kFunctionReference:
116 case Expression::Kind::kMethodReference:
117 case Expression::Kind::kTypeReference:
118 SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()");
119 fContext.fErrors->error(expr.fLine, "invalid expression");
120 break;
121 default:
122 if (expr.type().matches(*fContext.fTypes.fInvalid)) {
123 fContext.fErrors->error(expr.fLine, "invalid expression");
124 }
125 break;
126 }
127 return INHERITED::visitExpression(expr);
128 }
129
130 private:
131 using INHERITED = ProgramVisitor;
132 size_t fGlobalSlotsUsed = 0;
133 const Context& fContext;
134 const ProgramUsage& fUsage;
135 };
136
137 } // namespace
138
DoFinalizationChecks(const Program & program)139 void Analysis::DoFinalizationChecks(const Program& program) {
140 // Check all of the program's owned elements. (Built-in elements are assumed to be valid.)
141 FinalizationVisitor visitor{*program.fContext, *program.usage()};
142 for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
143 visitor.visitProgramElement(*element);
144 }
145 }
146
147 } // namespace SkSL
148