Description
Related Pull Request
#1525
Related Stack overflow questions:
https://stackoverflow.com/search?q=%5Bfirebase-functions%5D+Maximum+call+stack+size+exceeded
[REQUIRED] Version info
node: all versions of node, tested in 18
firebase-functions: all
firebase-tools: irrelevant
firebase-admin: irrelevant
[REQUIRED] Test case
A class instance with self-referencing properties causes a "Maximum call stack size exceeded" error when encoded. Example class:
class TestClass {
foo: string;
bar: number;
self: TestClass;
constructor(foo: string, bar: number) {
this.foo = foo;
this.bar = bar;
this.self = this;
}
}
const functions = require('firebase-functions');
exports.failingCode = functions.https.onCall((data, context) => {
return new TestClass("hello",1);
});
[REQUIRED] Steps to reproduce
Define a class with a self-referencing property.
Create an instance of this class.
Attempt to return the object
[REQUIRED] Expected behavior
The object should be encoded without resulting in a stack overflow, properly handling self-references within the object structure.
[REQUIRED] Actual behavior
Encoding such an object leads to a firebase on call /workspace/node_modules/firebase-functions/lib/common/providers/https.js "Maximum call stack size exceeded"
error, indicating an unhandled recursive loop in the encoding process.
Were you able to successfully deploy your functions?
Yes, but the function execution fails when encountering objects with self-references.
EDIT:
actual code from repo with the error (simplified):
const functions = require('firebase-functions');
exports.getAllSuperAdmins = functions.https.onCall((data, context) => {
const totalAdmins: DbUserModel[] = [];
for await (const users of firestoreUsersPaginator([Filter.where('isAdmin', '==', true)])) {
logger.log('get all super admins', users.length);
// checking that the users have the right claims and update docs without the right claims
totalAdmins.push(...(await filterAndFixUsersWithInvalidData(users)));
logger.log('totalAdmins', totalAdmins.length);
}
return totalAdmins
});
async function* firestoreUsersPaginator(customQuery: Filter[] = [], pageSize: number = 1000) {
pageSize = Math.min(pageSize, 1000); // max page size is 1000
const baseQuery = admin
.firestore()
.collection('users') as firestore.CollectionReference<DbUserModel>;
let query: FirebaseFirestore.Query<DbUserModel> = baseQuery;
for (const q of customQuery) {
query = query.where(q);
}
query = query.orderBy('__name__').limit(pageSize);
let pageLastDoc: firestore.QueryDocumentSnapshot<DbUserModel> | undefined = undefined;
let i = 0;
while (true) {
const pageQuery: firestore.Query<DbUserModel> = pageLastDoc
? query.startAfter(pageLastDoc.id)
: query;
const usersDocs = await pageQuery.get();
// pagination logic
}
return 'OK';
}