xref: /aosp_15_r20/external/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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