Interpreter: Disallow recursion
I think this is the minimum rule that's easy to understand when writing
SkSL for the interpreter that ensures we'll be able to statically
determine total stack usage of a particular function.
While writing the new test, I also noticed that we still return
(invalid) byte code, even when there are errors. Fixed that.
Change-Id: I625a8592c9ba1656074e5f0d4227d41968af7b37
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/226218
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
index e86dd86..5a8030a 100644
--- a/src/sksl/SkSLByteCodeGenerator.cpp
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -81,12 +81,7 @@
; // ignore
}
}
- for (auto& call : fCallTargets) {
- if (!call.set()) {
- return false;
- }
- }
- return true;
+ return 0 == fErrors.errorCount();
}
std::unique_ptr<ByteCodeFunction> ByteCodeGenerator::writeFunction(const FunctionDefinition& f) {
@@ -669,7 +664,23 @@
return;
}
- // Otherwise, we may need to deal with out parameters, so the sequence is trickier...
+ // Find the index of the function we're calling. We explicitly do not allow calls to functions
+ // before they're defined. This is an easy-to-understand rule that prevents recursion.
+ size_t idx;
+ for (idx = 0; idx < fFunctions.size(); ++idx) {
+ if (f.fFunction.matches(fFunctions[idx]->fDeclaration)) {
+ break;
+ }
+ }
+ if (idx > 255) {
+ fErrors.error(f.fOffset, "Function count limit exceeded");
+ return;
+ } else if (idx >= fFunctions.size()) {
+ fErrors.error(f.fOffset, "Call to undefined function");
+ return;
+ }
+
+ // We may need to deal with out parameters, so the sequence is tricky
if (int returnCount = SlotCount(f.fType)) {
this->write(ByteCodeInstruction::kReserve);
this->write8(returnCount);
@@ -689,7 +700,7 @@
}
this->write(ByteCodeInstruction::kCall);
- fCallTargets.emplace_back(this, f.fFunction);
+ this->write8(idx);
// After the called function returns, the stack will still contain our arguments. We have to
// pop them (storing any out parameters back to their lvalues as we go). We glob together slot