1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2021 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkTHash.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLAnalysis.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLContext.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLErrorReporter.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/analysis/SkSLProgramVisitor.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLExpression.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLFunctionCall.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLFunctionDeclaration.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLFunctionDefinition.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLProgram.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLProgramElement.h"
20*c8dee2aaSAndroid Build Coastguard Worker
21*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
22*c8dee2aaSAndroid Build Coastguard Worker #include <memory>
23*c8dee2aaSAndroid Build Coastguard Worker #include <string>
24*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
25*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
26*c8dee2aaSAndroid Build Coastguard Worker
27*c8dee2aaSAndroid Build Coastguard Worker namespace SkSL {
28*c8dee2aaSAndroid Build Coastguard Worker
CheckProgramStructure(const Program & program)29*c8dee2aaSAndroid Build Coastguard Worker bool Analysis::CheckProgramStructure(const Program& program) {
30*c8dee2aaSAndroid Build Coastguard Worker const Context& context = *program.fContext;
31*c8dee2aaSAndroid Build Coastguard Worker
32*c8dee2aaSAndroid Build Coastguard Worker static constexpr size_t kProgramStackDepthLimit = 50;
33*c8dee2aaSAndroid Build Coastguard Worker
34*c8dee2aaSAndroid Build Coastguard Worker class ProgramStructureVisitor : public ProgramVisitor {
35*c8dee2aaSAndroid Build Coastguard Worker public:
36*c8dee2aaSAndroid Build Coastguard Worker ProgramStructureVisitor(const Context& c) : fContext(c) {}
37*c8dee2aaSAndroid Build Coastguard Worker
38*c8dee2aaSAndroid Build Coastguard Worker using ProgramVisitor::visitProgramElement;
39*c8dee2aaSAndroid Build Coastguard Worker
40*c8dee2aaSAndroid Build Coastguard Worker bool visitProgramElement(const ProgramElement& pe) override {
41*c8dee2aaSAndroid Build Coastguard Worker if (pe.is<FunctionDefinition>()) {
42*c8dee2aaSAndroid Build Coastguard Worker // Check the function map first. We don't need to visit this function if we already
43*c8dee2aaSAndroid Build Coastguard Worker // processed it before.
44*c8dee2aaSAndroid Build Coastguard Worker const FunctionDeclaration* decl = &pe.as<FunctionDefinition>().declaration();
45*c8dee2aaSAndroid Build Coastguard Worker if (FunctionState *funcState = fFunctionMap.find(decl)) {
46*c8dee2aaSAndroid Build Coastguard Worker // We already have this function in our map. We don't need to check it again.
47*c8dee2aaSAndroid Build Coastguard Worker if (*funcState == FunctionState::kVisiting) {
48*c8dee2aaSAndroid Build Coastguard Worker // If the function is present in the map with with the `kVisiting` state,
49*c8dee2aaSAndroid Build Coastguard Worker // we're recursively processing it -- in other words, we found a cycle in
50*c8dee2aaSAndroid Build Coastguard Worker // the code. Unwind our stack into a string.
51*c8dee2aaSAndroid Build Coastguard Worker std::string msg = "\n\t" + decl->description();
52*c8dee2aaSAndroid Build Coastguard Worker for (auto unwind = fStack.rbegin(); unwind != fStack.rend(); ++unwind) {
53*c8dee2aaSAndroid Build Coastguard Worker msg = "\n\t" + (*unwind)->description() + msg;
54*c8dee2aaSAndroid Build Coastguard Worker if (*unwind == decl) {
55*c8dee2aaSAndroid Build Coastguard Worker break;
56*c8dee2aaSAndroid Build Coastguard Worker }
57*c8dee2aaSAndroid Build Coastguard Worker }
58*c8dee2aaSAndroid Build Coastguard Worker msg = "potential recursion (function call cycle) not allowed:" + msg;
59*c8dee2aaSAndroid Build Coastguard Worker fContext.fErrors->error(pe.fPosition, std::move(msg));
60*c8dee2aaSAndroid Build Coastguard Worker *funcState = FunctionState::kVisited;
61*c8dee2aaSAndroid Build Coastguard Worker return true;
62*c8dee2aaSAndroid Build Coastguard Worker }
63*c8dee2aaSAndroid Build Coastguard Worker return false;
64*c8dee2aaSAndroid Build Coastguard Worker }
65*c8dee2aaSAndroid Build Coastguard Worker
66*c8dee2aaSAndroid Build Coastguard Worker // If the function-call stack has gotten too deep, stop the analysis.
67*c8dee2aaSAndroid Build Coastguard Worker if (fStack.size() >= kProgramStackDepthLimit) {
68*c8dee2aaSAndroid Build Coastguard Worker std::string msg = "exceeded max function call depth:";
69*c8dee2aaSAndroid Build Coastguard Worker for (auto unwind = fStack.begin(); unwind != fStack.end(); ++unwind) {
70*c8dee2aaSAndroid Build Coastguard Worker msg += "\n\t" + (*unwind)->description();
71*c8dee2aaSAndroid Build Coastguard Worker }
72*c8dee2aaSAndroid Build Coastguard Worker msg += "\n\t" + decl->description();
73*c8dee2aaSAndroid Build Coastguard Worker fContext.fErrors->error(pe.fPosition, std::move(msg));
74*c8dee2aaSAndroid Build Coastguard Worker fFunctionMap.set(decl, FunctionState::kVisited);
75*c8dee2aaSAndroid Build Coastguard Worker return true;
76*c8dee2aaSAndroid Build Coastguard Worker }
77*c8dee2aaSAndroid Build Coastguard Worker
78*c8dee2aaSAndroid Build Coastguard Worker fFunctionMap.set(decl, FunctionState::kVisiting);
79*c8dee2aaSAndroid Build Coastguard Worker fStack.push_back(decl);
80*c8dee2aaSAndroid Build Coastguard Worker bool result = INHERITED::visitProgramElement(pe);
81*c8dee2aaSAndroid Build Coastguard Worker fFunctionMap.set(decl, FunctionState::kVisited);
82*c8dee2aaSAndroid Build Coastguard Worker fStack.pop_back();
83*c8dee2aaSAndroid Build Coastguard Worker
84*c8dee2aaSAndroid Build Coastguard Worker return result;
85*c8dee2aaSAndroid Build Coastguard Worker }
86*c8dee2aaSAndroid Build Coastguard Worker
87*c8dee2aaSAndroid Build Coastguard Worker return INHERITED::visitProgramElement(pe);
88*c8dee2aaSAndroid Build Coastguard Worker }
89*c8dee2aaSAndroid Build Coastguard Worker
90*c8dee2aaSAndroid Build Coastguard Worker bool visitExpression(const Expression& expr) override {
91*c8dee2aaSAndroid Build Coastguard Worker bool earlyExit = false;
92*c8dee2aaSAndroid Build Coastguard Worker
93*c8dee2aaSAndroid Build Coastguard Worker if (expr.is<FunctionCall>()) {
94*c8dee2aaSAndroid Build Coastguard Worker const FunctionCall& call = expr.as<FunctionCall>();
95*c8dee2aaSAndroid Build Coastguard Worker const FunctionDeclaration* decl = &call.function();
96*c8dee2aaSAndroid Build Coastguard Worker if (decl->definition() && !decl->isIntrinsic()) {
97*c8dee2aaSAndroid Build Coastguard Worker earlyExit = this->visitProgramElement(*decl->definition());
98*c8dee2aaSAndroid Build Coastguard Worker }
99*c8dee2aaSAndroid Build Coastguard Worker }
100*c8dee2aaSAndroid Build Coastguard Worker
101*c8dee2aaSAndroid Build Coastguard Worker return earlyExit || INHERITED::visitExpression(expr);
102*c8dee2aaSAndroid Build Coastguard Worker }
103*c8dee2aaSAndroid Build Coastguard Worker
104*c8dee2aaSAndroid Build Coastguard Worker private:
105*c8dee2aaSAndroid Build Coastguard Worker using INHERITED = ProgramVisitor;
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker enum class FunctionState {
108*c8dee2aaSAndroid Build Coastguard Worker kVisiting,
109*c8dee2aaSAndroid Build Coastguard Worker kVisited,
110*c8dee2aaSAndroid Build Coastguard Worker };
111*c8dee2aaSAndroid Build Coastguard Worker
112*c8dee2aaSAndroid Build Coastguard Worker const Context& fContext;
113*c8dee2aaSAndroid Build Coastguard Worker skia_private::THashMap<const FunctionDeclaration*, FunctionState> fFunctionMap;
114*c8dee2aaSAndroid Build Coastguard Worker std::vector<const FunctionDeclaration*> fStack;
115*c8dee2aaSAndroid Build Coastguard Worker };
116*c8dee2aaSAndroid Build Coastguard Worker
117*c8dee2aaSAndroid Build Coastguard Worker // Process every function in our program.
118*c8dee2aaSAndroid Build Coastguard Worker ProgramStructureVisitor visitor{context};
119*c8dee2aaSAndroid Build Coastguard Worker for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
120*c8dee2aaSAndroid Build Coastguard Worker if (element->is<FunctionDefinition>()) {
121*c8dee2aaSAndroid Build Coastguard Worker // Visit every function--we want to detect static recursion and report it as an error,
122*c8dee2aaSAndroid Build Coastguard Worker // even in unreferenced functions.
123*c8dee2aaSAndroid Build Coastguard Worker visitor.visitProgramElement(*element);
124*c8dee2aaSAndroid Build Coastguard Worker }
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker
127*c8dee2aaSAndroid Build Coastguard Worker return true;
128*c8dee2aaSAndroid Build Coastguard Worker }
129*c8dee2aaSAndroid Build Coastguard Worker
130*c8dee2aaSAndroid Build Coastguard Worker } // namespace SkSL
131