firebase-functions: Firebase Functions Encoding Failure for Self-Referencing Objects Leads to Maximum call stack size exceeded

Related issues #737 #776 #844

Related Pull Request #1525

Related Stack overflow questions: https://stackoverflow.com/search?q=[firebase-functions]+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';
}

About this issue

  • Original URL
  • State: closed
  • Created 4 months ago
  • Reactions: 2
  • Comments: 15 (5 by maintainers)

Most upvoted comments

@inlined,

Thank you for reconsidering this issue and for the detailed exploration of potential solutions. I greatly appreciate the effort to find a compromise that balances developer needs with system integrity.

I agree with the hesitation regarding the automatic deep copying of circular references due to the potential for subtle failures and the increased cost implications. The option to emit a warning when circular references are dropped resonates with me as a balanced approach. It informs developers of the modification while maintaining the system’s integrity and avoiding the complexities of deep copying.

Introducing a global option and a per-usage override for handling circular references offers the flexibility needed for various use cases. This approach allows developers to make informed decisions based on their specific requirements and contexts, which I believe is a valuable enhancement to the system.

I’m supportive of your preference for option 2, with a focus on warning developers about dropped circular references. This seems to be a pragmatic solution that enhances developer awareness without overly complicating the system.

I’m looking forward to seeing how these ideas evolve and am happy to provide further input or assist in testing potential implementations.

I would love to be part of this changes once the team decided on how to approach this.

Regarding the circular reference in my example, I suspect it originated from referencing a document in another database. Unfortunately, due to the nature of the error and lack of detailed trace context, I was unable to conclusively verify this. The inability to easily identify the source of circular references in complex objects underscores the importance of enhancing error reporting and handling mechanisms.