Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkSLCanExitWithoutReturningValue.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 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
20
21#include <memory>
22
23namespace SkSL {
24class Expression;
25namespace {
26
27class ReturnsOnAllPathsVisitor : public ProgramVisitor {
28public:
29 bool visitExpression(const Expression& expr) override {
30 // We can avoid processing expressions entirely.
31 return false;
32 }
33
34 bool visitStatement(const Statement& stmt) override {
35 switch (stmt.kind()) {
36 // Returns, breaks, or continues will stop the scan, so only one of these should ever be
37 // true.
38 case Statement::Kind::kReturn:
39 fFoundReturn = true;
40 return true;
41
42 case Statement::Kind::kBreak:
43 fFoundBreak = true;
44 return true;
45
46 case Statement::Kind::kContinue:
47 fFoundContinue = true;
48 return true;
49
50 case Statement::Kind::kIf: {
51 const IfStatement& i = stmt.as<IfStatement>();
52 ReturnsOnAllPathsVisitor trueVisitor;
53 ReturnsOnAllPathsVisitor falseVisitor;
54 trueVisitor.visitStatement(*i.ifTrue());
55 if (i.ifFalse()) {
56 falseVisitor.visitStatement(*i.ifFalse());
57 }
58 // If either branch leads to a break or continue, we report the entire if as
59 // containing a break or continue, since we don't know which side will be reached.
60 fFoundBreak = (trueVisitor.fFoundBreak || falseVisitor.fFoundBreak);
61 fFoundContinue = (trueVisitor.fFoundContinue || falseVisitor.fFoundContinue);
62 // On the other hand, we only want to report returns that definitely happen, so we
63 // require those to be found on both sides.
64 fFoundReturn = (trueVisitor.fFoundReturn && falseVisitor.fFoundReturn);
65 return fFoundBreak || fFoundContinue || fFoundReturn;
66 }
67 case Statement::Kind::kFor: {
68 const ForStatement& f = stmt.as<ForStatement>();
69 // We assume a for/while loop runs for at least one iteration; this isn't strictly
70 // guaranteed, but it's better to be slightly over-permissive here than to fail on
71 // reasonable code.
72 ReturnsOnAllPathsVisitor forVisitor;
73 forVisitor.visitStatement(*f.statement());
74 // A for loop that contains a break or continue is safe; it won't exit the entire
75 // function, just the loop. So we disregard those signals.
76 fFoundReturn = forVisitor.fFoundReturn;
77 return fFoundReturn;
78 }
79 case Statement::Kind::kDo: {
80 const DoStatement& d = stmt.as<DoStatement>();
81 // Do-while blocks are always entered at least once.
82 ReturnsOnAllPathsVisitor doVisitor;
83 doVisitor.visitStatement(*d.statement());
84 // A do-while loop that contains a break or continue is safe; it won't exit the
85 // entire function, just the loop. So we disregard those signals.
86 fFoundReturn = doVisitor.fFoundReturn;
87 return fFoundReturn;
88 }
89 case Statement::Kind::kBlock:
90 // Blocks are definitely entered and don't imply any additional control flow.
91 // If the block contains a break, continue or return, we want to keep that.
92 return INHERITED::visitStatement(stmt);
93
94 case Statement::Kind::kSwitch: {
95 // Switches are the most complex control flow we need to deal with; fortunately we
96 // already have good primitives for dissecting them. We need to verify that:
97 // - a default case exists, so that every possible input value is covered
98 // - every switch-case either (a) returns unconditionally, or
99 // (b) falls through to another case that does
100 const SwitchStatement& s = stmt.as<SwitchStatement>();
101 bool foundDefault = false;
102 bool fellThrough = false;
103 for (const std::unique_ptr<Statement>& switchStmt : s.cases()) {
104 // The default case is indicated by a null value. A switch without a default
105 // case cannot definitively return, as its value might not be in the cases list.
106 const SwitchCase& sc = switchStmt->as<SwitchCase>();
107 if (sc.isDefault()) {
108 foundDefault = true;
109 }
110 // Scan this switch-case for any exit (break, continue or return).
111 ReturnsOnAllPathsVisitor caseVisitor;
112 caseVisitor.visitStatement(sc);
113
114 // If we found a break or continue, whether conditional or not, this switch case
115 // can't be called an unconditional return. Switches absorb breaks but not
116 // continues.
117 if (caseVisitor.fFoundContinue) {
118 fFoundContinue = true;
119 return false;
120 }
121 if (caseVisitor.fFoundBreak) {
122 return false;
123 }
124 // We just confirmed that there weren't any breaks or continues. If we didn't
125 // find an unconditional return either, the switch is considered fallen-through.
126 // (There might be a conditional return, but that doesn't count.)
127 fellThrough = !caseVisitor.fFoundReturn;
128 }
129
130 // If we didn't find a default case, or the very last case fell through, this switch
131 // doesn't meet our criteria.
132 if (fellThrough || !foundDefault) {
133 return false;
134 }
135
136 // We scanned the entire switch, found a default case, and every section either fell
137 // through or contained an unconditional return.
138 fFoundReturn = true;
139 return true;
140 }
141
142 case Statement::Kind::kSwitchCase:
143 // Recurse into the switch-case.
144 return INHERITED::visitStatement(stmt);
145
146 case Statement::Kind::kDiscard:
147 case Statement::Kind::kExpression:
148 case Statement::Kind::kNop:
149 case Statement::Kind::kVarDeclaration:
150 // None of these statements could contain a return.
151 break;
152 }
153
154 return false;
155 }
156
157 bool fFoundReturn = false;
158 bool fFoundBreak = false;
159 bool fFoundContinue = false;
160
161 using INHERITED = ProgramVisitor;
162};
163
164} // namespace
165
167 const Statement& body) {
168 if (funcDecl.returnType().isVoid()) {
169 return false;
170 }
171 ReturnsOnAllPathsVisitor visitor;
172 visitor.visitStatement(body);
173 return !visitor.fFoundReturn;
174}
175
176} // namespace SkSL
#define INHERITED(method,...)
bool isVoid() const
Definition SkSLType.h:496
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
struct MyStruct s
bool CanExitWithoutReturningValue(const FunctionDeclaration &funcDecl, const Statement &body)