Skip to content

Firebase Functions Encoding Failure for Self-Referencing Objects Leads to Maximum call stack size exceeded #1527

Closed
@DavidWeiss2

Description

@DavidWeiss2

Related issues
#737 #776 #844

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';
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions