Rector: Memory leak with Laravel IDE helper loaded

Created on 30 Jul 2020  路  12Comments  路  Source: rectorphp/rector

Bug Report

| Subject | Details |
| :------------- | :---------------------------------------------------------------|
| Rector version | dev-master |
| Installed as | composer dependency |

So I am using Laravel IDE helper to have better suggestions in IDE. There's a _ide_loader.php file which has some classes which I then include as @mixin in PHPDoc of Models.

When I try to run rector on such code

<?php

namespace App\Services;

use App\Models\Cart;
use Auth;
use Session;


class CartService
{
    /**
     * @return Cart
     */
    function initNewCart()
    {
        $cart = new Cart();
        $cart->price = 0.0; // this line causes it
        return $cart;
    }
}

It bloats the memory over 4G and causes a segmentation fault because lack of memory available.

The underlying property price doesn't exist, it's only as a @property float $price and is available via Laravel's Model magic. The problem is that BaseModel, which I am extending in Cart class, is defined on two places: that generated ide helper & actual BaseModel class. If I load the ide helper file, it goes into fire.

image

image

When I don't autoload that file (which I should not ever load, it's only for ide's knowledge), it screams about the @mixin Eloquent I have in some of my models:

/**
 * @property int $id
 * ...
 * @mixin Eloquent
 */
class Store extends BaseModel {

}
 [ERROR] Could not process "app/Services/StoreResolver.php" file, due to:                                               
         "Analyze error: "Class Eloquent not found.". Include your files in "parameters > autoload_paths".              
         See https://github.com/rectorphp/rector#extra-autoloading".                                                    

Most helpful comment

I don't know how rector works. If you have a possiblity to have your own PHPStan config, just add

parameters:
    mixinExcludeClasses:
        - Eloquent

All 12 comments

Is there a way I could explicitly disable checking @mixins?

It might be something in Cart. If you replace Cart with some dummy empty class, does the problem remain?

Is there a way I could explicitly disable checking @mixins?

Not sure what does it mean, I never used Laravel. Could you share reproducible minimal code so we can see error ourselves?

Not sure what IDE helper is, but if it makes mess, you can exclude it.

It might be something in Cart. If you replace Cart with some dummy empty class, does the problem remain?
As soon as I remove the line I highlighted with comment, it works properly. IDE helper is simply recreating all the classes that have magic methods from Laravel and adds extra typehints, return types and more useful things. That's not necessary to underestand tho.

The problem is that rector, or some part of rector, is analyzing content DOCblock, finding @mixin Eloquent and tries to autoload that class, which is present only for IDE to recognize more methods because they are being called magically making it unable for IDE to hint it without the helper. It's not meant to be loaded, autoloaded, by PHP itself. Rector is trying to load it, which means autoload, the file, and is unable to continue without it. That file alone has 22 000 lines of code which can be a part of the reason why it uses so much memory.

Hm, black magic strikes again.

What do you suggest to keep PHPStan/Rector working on other projects but not get stuck on your code?

I am digging deeper to narrow it down to the smallest possible example I can make.

So here are things that worked out:

_ide_helper.php is now narrowed down to

<?php

    class Eloquent extends \Illuminate\Database\Eloquent\Model {

        }

Model.php looks like this at the top:

/**
 * 
 * @mixin Eloquent
 * @mixin \Illuminate\Database\Eloquent\Builder
 * @mixin \Illuminate\Database\Query\Builder
 */
abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
  • If I remove extends from Eloquent class, it works
  • If I remove @mixin from Model, it works
  • If I remove $cart->price = 0.0; from the provided example, it works
  • If I add @mixin in Cart, it works.

I thought it's some sort of infinite loop because class that Model is using @mixin and that Mixin is extending Model. But that's not the case if I try it in isolated environment.

Then I went to try to narrow down the part of code of rector causing this.

PHPStanNodeScopeResolver.php - I added these echoes

      $nodeCallback = function (Node $node, MutatingScope $scope) use ($smartFileInfo): void {
            echo "A";
            // the class reflection is resolved AFTER entering to class node
            // so we need to get it from the first after this one
            if ($node instanceof Class_ || $node instanceof Interface_) {
                $scope = $this->resolveClassOrInterfaceScope($node, $scope);
            }
echo "b";
            // traversing trait inside class that is using it scope (from referenced) - the trait traversed by Rector is different (directly from parsed file)
            if ($scope->isInTrait()) {
                $traitName = $scope->getTraitReflection()->getName();
                $this->traitNodeScopeCollector->addForTraitAndNode($traitName, $node, $scope);

                return;
            }
            echo $smartFileInfo->getFilename();
echo "c";
            // special case for unreachable nodes
            if ($node instanceof UnreachableStatementNode) {
                $originalNode = $node->getOriginalStatement();
                $originalNode->setAttribute(AttributeKey::IS_UNREACHABLE, true);
                $originalNode->setAttribute(AttributeKey::SCOPE, $scope);
            } else {
                $node->setAttribute(AttributeKey::SCOPE, $scope);
            }
echo "c2";
            $this->resolveDependentFiles($node, $scope);
            echo "c3";

        };

and also var_dumped the nodes:

        var_dump($nodes);
        /** @var MutatingScope $scope */
        $this->nodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);

This is what I get if I have Eloquent mixin in Model:

Rector dev-master@7dae020
Config file: rector.php

[parsing] app/Services/CartService.php
array(1) {
  [0]=>
  object(PhpParser\Node\Stmt\Namespace_)#1776 (3) {
    ["name"]=>
    object(PhpParser\Node\Name)#1792 (2) {
      ["parts"]=>
      array(2) {
        [0]=>
        string(3) "App"
        [1]=>
        string(8) "Services"
      }
      ["attributes":protected]=>
      array(4) {
        ["startLine"]=>
        int(3)
        ["startTokenPos"]=>
        int(4)
        ["endLine"]=>
        int(3)
        ["endTokenPos"]=>
        int(4)
      }
    }
    ["stmts"]=>
    array(4) {
      [0]=>
      object(PhpParser\Node\Stmt\Use_)#1775 (3) {
        ["type"]=>
        int(1)
        ["uses"]=>
        array(1) {
          [0]=>
          object(PhpParser\Node\Stmt\UseUse)#1615 (4) {
            ["type"]=>
            int(0)
            ["name"]=>
            object(PhpParser\Node\Name)#1687 (2) {
              ["parts"]=>
              array(3) {
                [0]=>
                string(3) "App"
                [1]=>
                string(6) "Models"
                [2]=>
                string(4) "Cart"
              }
              ["attributes":protected]=>
              array(4) {
                ["startLine"]=>
                int(5)
                ["startTokenPos"]=>
                int(9)
                ["endLine"]=>
                int(5)
                ["endTokenPos"]=>
                int(9)
              }
            }
            ["alias"]=>
            NULL
            ["attributes":protected]=>
            array(4) {
              ["startLine"]=>
              int(5)
              ["startTokenPos"]=>
              int(9)
              ["endLine"]=>
              int(5)
              ["endTokenPos"]=>
              int(9)
            }
          }
        }
        ["attributes":protected]=>
        array(4) {
          ["startLine"]=>
          int(5)
          ["startTokenPos"]=>
          int(7)
          ["endLine"]=>
          int(5)
          ["endTokenPos"]=>
          int(10)
        }
      }
      [1]=>
      object(PhpParser\Node\Stmt\Use_)#1796 (3) {
        ["type"]=>
        int(1)
        ["uses"]=>
        array(1) {
          [0]=>
          object(PhpParser\Node\Stmt\UseUse)#1789 (4) {
            ["type"]=>
            int(0)
            ["name"]=>
            object(PhpParser\Node\Name)#1791 (2) {
              ["parts"]=>
              array(1) {
                [0]=>
                string(4) "Auth"
              }
              ["attributes":protected]=>
              array(4) {
                ["startLine"]=>
                int(6)
                ["startTokenPos"]=>
                int(14)
                ["endLine"]=>
                int(6)
                ["endTokenPos"]=>
                int(14)
              }
            }
            ["alias"]=>
            NULL
            ["attributes":protected]=>
            array(4) {
              ["startLine"]=>
              int(6)
              ["startTokenPos"]=>
              int(14)
              ["endLine"]=>
              int(6)
              ["endTokenPos"]=>
              int(14)
            }
          }
        }
        ["attributes":protected]=>
        array(4) {
          ["startLine"]=>
          int(6)
          ["startTokenPos"]=>
          int(12)
          ["endLine"]=>
          int(6)
          ["endTokenPos"]=>
          int(15)
        }
      }
      [2]=>
      object(PhpParser\Node\Stmt\Use_)#1807 (3) {
        ["type"]=>
        int(1)
        ["uses"]=>
        array(1) {
          [0]=>
          object(PhpParser\Node\Stmt\UseUse)#1798 (4) {
            ["type"]=>
            int(0)
            ["name"]=>
            object(PhpParser\Node\Name)#1808 (2) {
              ["parts"]=>
              array(1) {
                [0]=>
                string(7) "Session"
              }
              ["attributes":protected]=>
              array(4) {
                ["startLine"]=>
                int(7)
                ["startTokenPos"]=>
                int(19)
                ["endLine"]=>
                int(7)
                ["endTokenPos"]=>
                int(19)
              }
            }
            ["alias"]=>
            NULL
            ["attributes":protected]=>
            array(4) {
              ["startLine"]=>
              int(7)
              ["startTokenPos"]=>
              int(19)
              ["endLine"]=>
              int(7)
              ["endTokenPos"]=>
              int(19)
            }
          }
        }
        ["attributes":protected]=>
        array(4) {
          ["startLine"]=>
          int(7)
          ["startTokenPos"]=>
          int(17)
          ["endLine"]=>
          int(7)
          ["endTokenPos"]=>
          int(20)
        }
      }
      [3]=>
      object(PhpParser\Node\Stmt\Class_)#1810 (7) {
        ["flags"]=>
        int(0)
        ["extends"]=>
        NULL
        ["implements"]=>
        array(0) {
        }
        ["name"]=>
        object(PhpParser\Node\Identifier)#1823 (2) {
          ["name"]=>
          string(11) "CartService"
          ["attributes":protected]=>
          array(4) {
            ["startLine"]=>
            int(9)
            ["startTokenPos"]=>
            int(24)
            ["endLine"]=>
            int(9)
            ["endTokenPos"]=>
            int(24)
          }
        }
        ["stmts"]=>
        array(1) {
          [0]=>
          object(PhpParser\Node\Stmt\ClassMethod)#1818 (7) {
            ["flags"]=>
            int(0)
            ["byRef"]=>
            bool(false)
            ["name"]=>
            object(PhpParser\Node\Identifier)#1887 (2) {
              ["name"]=>
              string(11) "initNewCart"
              ["attributes":protected]=>
              array(4) {
                ["startLine"]=>
                int(11)
                ["startTokenPos"]=>
                int(30)
                ["endLine"]=>
                int(11)
                ["endTokenPos"]=>
                int(30)
              }
            }
            ["params"]=>
            array(0) {
            }
            ["returnType"]=>
            NULL
            ["stmts"]=>
            array(3) {
              [0]=>
              object(PhpParser\Node\Stmt\Expression)#1800 (2) {
                ["expr"]=>
                object(PhpParser\Node\Expr\Assign)#2023 (3) {
                  ["var"]=>
                  object(PhpParser\Node\Expr\Variable)#1820 (2) {
                    ["name"]=>
                    string(4) "cart"
                    ["attributes":protected]=>
                    array(4) {
                      ["startLine"]=>
                      int(13)
                      ["startTokenPos"]=>
                      int(36)
                      ["endLine"]=>
                      int(13)
                      ["endTokenPos"]=>
                      int(36)
                    }
                  }
                  ["expr"]=>
                  object(PhpParser\Node\Expr\New_)#1880 (3) {
                    ["class"]=>
                    object(PhpParser\Node\Name\FullyQualified)#1815 (2) {
                      ["parts"]=>
                      array(3) {
                        [0]=>
                        string(3) "App"
                        [1]=>
                        string(6) "Models"
                        [2]=>
                        string(4) "Cart"
                      }
                      ["attributes":protected]=>
                      array(5) {
                        ["startLine"]=>
                        int(13)
                        ["startTokenPos"]=>
                        int(42)
                        ["endLine"]=>
                        int(13)
                        ["endTokenPos"]=>
                        int(42)
                        ["originalName"]=>
                        object(PhpParser\Node\Name)#1933 (2) {
                          ["parts"]=>
                          array(1) {
                            [0]=>
                            string(4) "Cart"
                          }
                          ["attributes":protected]=>
                          array(4) {
                            ["startLine"]=>
                            int(13)
                            ["startTokenPos"]=>
                            int(42)
                            ["endLine"]=>
                            int(13)
                            ["endTokenPos"]=>
                            int(42)
                          }
                        }
                      }
                    }
                    ["args"]=>
                    array(0) {
                    }
                    ["attributes":protected]=>
                    array(4) {
                      ["startLine"]=>
                      int(13)
                      ["startTokenPos"]=>
                      int(40)
                      ["endLine"]=>
                      int(13)
                      ["endTokenPos"]=>
                      int(44)
                    }
                  }
                  ["attributes":protected]=>
                  array(4) {
                    ["startLine"]=>
                    int(13)
                    ["startTokenPos"]=>
                    int(36)
                    ["endLine"]=>
                    int(13)
                    ["endTokenPos"]=>
                    int(44)
                  }
                }
                ["attributes":protected]=>
                array(4) {
                  ["startLine"]=>
                  int(13)
                  ["startTokenPos"]=>
                  int(36)
                  ["endLine"]=>
                  int(13)
                  ["endTokenPos"]=>
                  int(45)
                }
              }
              [1]=>
              object(PhpParser\Node\Stmt\Expression)#1804 (2) {
                ["expr"]=>
                object(PhpParser\Node\Expr\Assign)#1814 (3) {
                  ["var"]=>
                  object(PhpParser\Node\Expr\PropertyFetch)#1995 (3) {
                    ["var"]=>
                    object(PhpParser\Node\Expr\Variable)#1799 (2) {
                      ["name"]=>
                      string(4) "cart"
                      ["attributes":protected]=>
                      array(4) {
                        ["startLine"]=>
                        int(14)
                        ["startTokenPos"]=>
                        int(47)
                        ["endLine"]=>
                        int(14)
                        ["endTokenPos"]=>
                        int(47)
                      }
                    }
                    ["name"]=>
                    object(PhpParser\Node\Identifier)#1806 (2) {
                      ["name"]=>
                      string(5) "price"
                      ["attributes":protected]=>
                      array(4) {
                        ["startLine"]=>
                        int(14)
                        ["startTokenPos"]=>
                        int(49)
                        ["endLine"]=>
                        int(14)
                        ["endTokenPos"]=>
                        int(49)
                      }
                    }
                    ["attributes":protected]=>
                    array(4) {
                      ["startLine"]=>
                      int(14)
                      ["startTokenPos"]=>
                      int(47)
                      ["endLine"]=>
                      int(14)
                      ["endTokenPos"]=>
                      int(49)
                    }
                  }
                  ["expr"]=>
                  object(PhpParser\Node\Scalar\DNumber)#1802 (2) {
                    ["value"]=>
                    float(0)
                    ["attributes":protected]=>
                    array(4) {
                      ["startLine"]=>
                      int(14)
                      ["startTokenPos"]=>
                      int(53)
                      ["endLine"]=>
                      int(14)
                      ["endTokenPos"]=>
                      int(53)
                    }
                  }
                  ["attributes":protected]=>
                  array(4) {
                    ["startLine"]=>
                    int(14)
                    ["startTokenPos"]=>
                    int(47)
                    ["endLine"]=>
                    int(14)
                    ["endTokenPos"]=>
                    int(53)
                  }
                }
                ["attributes":protected]=>
                array(4) {
                  ["startLine"]=>
                  int(14)
                  ["startTokenPos"]=>
                  int(47)
                  ["endLine"]=>
                  int(14)
                  ["endTokenPos"]=>
                  int(54)
                }
              }
              [2]=>
              object(PhpParser\Node\Stmt\Return_)#1805 (2) {
                ["expr"]=>
                object(PhpParser\Node\Expr\Variable)#1803 (2) {
                  ["name"]=>
                  string(4) "cart"
                  ["attributes":protected]=>
                  array(4) {
                    ["startLine"]=>
                    int(15)
                    ["startTokenPos"]=>
                    int(58)
                    ["endLine"]=>
                    int(15)
                    ["endTokenPos"]=>
                    int(58)
                  }
                }
                ["attributes":protected]=>
                array(4) {
                  ["startLine"]=>
                  int(15)
                  ["startTokenPos"]=>
                  int(56)
                  ["endLine"]=>
                  int(15)
                  ["endTokenPos"]=>
                  int(59)
                }
              }
            }
            ["attributes":protected]=>
            array(4) {
              ["startLine"]=>
              int(11)
              ["startTokenPos"]=>
              int(28)
              ["endLine"]=>
              int(16)
              ["endTokenPos"]=>
              int(61)
            }
          }
        }
        ["attributes":protected]=>
        array(4) {
          ["startLine"]=>
          int(9)
          ["startTokenPos"]=>
          int(22)
          ["endLine"]=>
          int(17)
          ["endTokenPos"]=>
          int(63)
        }
        ["namespacedName"]=>
        object(PhpParser\Node\Name)#1817 (2) {
          ["parts"]=>
          array(3) {
            [0]=>
            string(3) "App"
            [1]=>
            string(8) "Services"
            [2]=>
            string(11) "CartService"
          }
          ["attributes":protected]=>
          array(0) {
          }
        }
      }
    }
    ["attributes":protected]=>
    array(5) {
      ["startLine"]=>
      int(3)
      ["startTokenPos"]=>
      int(2)
      ["endLine"]=>
      int(17)
      ["endTokenPos"]=>
      int(63)
      ["kind"]=>
      int(1)
    }
  }
}
AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartServ
ice.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3 (here it hangs)

If i remove that mixin in Model class, I get

File "/mnt/c/Users/admin/Dropbox/_php/RestaurantBack/vendor/autoload.php" is about to be loaded in "AutoloadIncluder::includeCwdVendorAutoloadIfExists()" on line 102"
File "/mnt/c/Users/admin/Dropbox/_php/RestaurantBack/vendor/rector/rector/../../autoload.php" is about to be loaded in "AutoloadIncluder::autoloadProjectAutoloaderFile()" on line 136"
Rector dev-master@7dae020
Config file: rector.php

[parsing] app/Services/CartService.php
array(1) {
    <<<the same var dump as above>>>
}
CartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartServ
ice.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2c3AbCartService.phpcc2
c3AbCartService.phpcc2c3AbCartService.phpcc2c3 (here it continues and finishes)

Unfortunately I wasn't narrow it down futher because it looks like It's hanging in NodeScopeResolver.php which is in phar, which I found even you had problem with finding solution to :-)

Maybe @ondrejmirtes could help a bit here, please?

It would be plenty enough to just have an ability to skip the @mixin from analyzing I think

PHPStan has an undocumented feature that you can tell it to ignore some @mixin tags. We use it at Larastan like this. Maybe this can help, if just the @mixin \Eloquent is the problem.

Just found about that haha. Thanks!
How do I set it up for rector usage tho? Is installing Larastan/setting in phpstan.neon enough?

I don't know how rector works. If you have a possiblity to have your own PHPStan config, just add

parameters:
    mixinExcludeClasses:
        - Eloquent
Was this page helpful?
0 / 5 - 0 ratings