@angular/compiler-cli downleveling decorators possibly causing RangeError: Maximum call stack size exceeded with isReferencedAliasDeclaration

🐞 bug report

Affected Package

The issue is caused by package @angular/compiler-cli version >=10

Is this a regression?

Yes, the previous version in which this issue was not happening was: 9

It started happening in Angular 10 with the introduction of decorator downleveling:

compiler-cli: downlevel angular decorators to static properties (#37382) (323651b), closes #30586 #30106 #30586 #30141

PR: #37382

Description

We just did an upgrade from Angular 9->11 and started seeing this problem in our biggest application when we run ng serve.

We have a monorepo setup with Nx. After the upgrade we were able to start 3 other applications that are a little bit smaller than previously mentioned one.

Since the error is quite unclear i started investigating what could be the cause and found only one issue report similar to this one here:

angular/angular-cli#18792

But that seemed to be an issue that was caused in their own setup. I also looked if we had anything similar, but with no luck.

I also tried doing divide and conquer approach removing some modules from the application and that made it work. However there was no exact rule which modules work and which dont, the distribution was quite random expect one thing if i made the app smaller, then it worked(by removing bigger modules).

Next on my list was to add some logging in patchAliasReferenceResolutionOrDie for the nodes that were being traversed and I did not see any circular dependency that would cause an infinite recursion.

Then i tried adjusting the implementation of isReferencedAliasDeclaration and was able to make this work by changing this part here:

// Before 
emitResolver.isReferencedAliasDeclaration = function(node, ...args) {
    if (isAliasImportDeclaration(node) && referencedAliases.has(node)) {
      return true;
    }
    return originalReferenceResolution.call(emitResolver, node, ...args);
  };

// After 
emitResolver.isReferencedAliasDeclaration = function(node, checkChildren) {
    if (isAliasImportDeclaration(node) && referencedAliases.has(node)) {
      return true;
    }
    return originalReferenceResolution.call(emitResolver, node, checkChildren);
  };

https://github.com/angular/angular/blob/master/packages/compiler-cli/src/transformers/patch_alias_reference_resolution.ts#L87

This change was made so that isReferencedAliasDeclaration looks exactly like the typescript implementation https://sourcegraph.com/github.com/microsoft/TypeScript@3eaa7c65f6f076a08a5f7f1946fd0df7c7430259/-/blob/src/compiler/checker.ts#L36179-36192

And this fixed the issue.

My 2 cents on why this fixed it is better explained when looking at compiled code of the function

// Before
emitResolver.isReferencedAliasDeclaration = function (node) {
    var args = [];
    for (var _i = 1, _len = arguments.length; _i < _len; _i++) {
      args[_i - 1] = arguments[_i];
    }
    if (isAliasImportDeclaration(node) && referencedAliases.has(node)) {
       return true;
    }
    return originalReferenceResolution.call.apply(originalReferenceResolution, tslib_1.__spread([emitResolver, node], args));
};

// After 
emitResolver.isReferencedAliasDeclaration = function (node, checkChildren) {
    if (isAliasImportDeclaration(node) && referencedAliases.has(node)) {
        return true;
    }
    return originalReferenceResolution.call(emitResolver, node, checkChildren);
};

During tree traversal isReferencedAliasDeclaration in each call is creating its own local array copying the arguments and then spreading that back again to original implementation, so i suppose after certain number of calls the memory just gets over allowed threshold and breaks.

🔥 Exception or Error




Error: RangeError: Maximum call stack size exceeded
    at Set.has ()
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:84:69)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)
    at Object.emitResolver.isReferencedAliasDeclaration (/monorepo/node_modules/@angular/compiler-cli/src/transformers/patch_alias_reference_resolution.js:87:53)

🌍 Your Environment

Angular Version:




    _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 11.0.5
Node: 12.18.2
OS: darwin x64

Angular: 11.0.5
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... platform-server, router
Ivy Workspace: Yes

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.1100.5
@angular-devkit/build-angular      0.1100.5
@angular-devkit/core               11.0.5
@angular-devkit/schematics         11.0.5
@angular/cdk                       11.0.3
@angular/flex-layout               11.0.0-beta.33
@angular/google-maps               11.0.3
@angular/material                  11.0.3
@angular/material-moment-adapter   11.0.3
@nguniversal/express-engine        11.0.1
@schematics/angular                11.0.5
@schematics/update                 0.1100.5
rxjs                               6.5.5
typescript                         4.0.5

Anything else relevant?

I am still not sure if this is something that is specific to our application, since i suppose there are bigger apps out there that would then have the same issue and would be known.

If you think this is something specific to us, can you please give some ideas how we could debug this further?

1 possible answer(s) on “@angular/compiler-cli downleveling decorators possibly causing RangeError: Maximum call stack size exceeded with isReferencedAliasDeclaration

  1. I did some more investigation and found that the ts.EmitResolver that is being patched is owned by the ts.TypeChecker, whereas the ts.TransformationContext is created multiple times for each source file. That explains why moving the line as suggested didn’t work, as already hinted by Pete.

    I have opened #40374 with a fix, which ensures that the patch is only applied once.