BetterReflection icon indicating copy to clipboard operation
BetterReflection copied to clipboard

EvalLoader doesn't handle autoloading additional classes well

Open WyriHaximus opened this issue 7 years ago • 10 comments

So I'm working on monkey patching some calls inside react/stream so it keeps working after prefixing fwrite and such with \ to \fwrite. @asgrim suggested I'd use better reflection for this. And loading and patching the classes works pretty well, up to the point where I want to run my unit tests. Not the classes I'm patching load another class and an interface and the EvalLoader just blows up at that point. My work can be found in this PR https://github.com/WyriHaximus/reactphp-inspector-stream/pull/1 and the exact error is:

HP Fatal error:  Class 'Evenement\EventEmitter' not found in /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoaderMethod/EvalLoader.php(25) : eval()'d code on line 3
PHP Stack trace:
PHP   1. {main}() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/phpunit:0
PHP   2. PHPUnit\TextUI\Command::main() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/phpunit:53
PHP   3. PHPUnit\TextUI\Command->run() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/Command.php:135
PHP   4. PHPUnit\TextUI\TestRunner->doRun() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/Command.php:206
PHP   5. PHPUnit\Framework\TestSuite->run() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:484
PHP   6. PHPUnit\Framework\TestSuite->run() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestSuite.php:739
PHP   7. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->run() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestSuite.php:739
PHP   8. PHPUnit\Framework\TestResult->run() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:867
PHP   9. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->runBare() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestResult.php:688
PHP  10. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->runTest() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:912
PHP  11. ReflectionMethod->invokeArgs() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:1053
PHP  12. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->testRerouting() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:1053
PHP  13. WyriHaximus\React\Inspector\Stream\Monky::patch() /home/travis/build/WyriHaximus/reactphp-inspector-stream/tests/MonkeyTest.php:14
PHP  14. Roave\BetterReflection\Reflector\ClassReflector->reflect() /home/travis/build/WyriHaximus/reactphp-inspector-stream/src/Monky.php:14
PHP  15. Roave\BetterReflection\SourceLocator\Type\MemoizingSourceLocator->locateIdentifier() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/Reflector/ClassReflector.php:35
PHP  16. Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator->locateIdentifier() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/MemoizingSourceLocator.php:40
PHP  17. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->locateIdentifier() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AggregateSourceLocator.php:38
PHP  18. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->createLocatedSource() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AbstractSourceLocator.php:42
PHP  19. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->attemptAutoloadForIdentifier() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:71
PHP  20. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->locateClassByName() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:91
PHP  21. class_exists() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:134
PHP  22. spl_autoload_call() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:134
PHP  23. Roave\BetterReflection\Util\Autoload\ClassLoader->__invoke() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:134
PHP  24. Roave\BetterReflection\Util\Autoload\ClassLoaderMethod\EvalLoader->__invoke() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoader.php:58
PHP  25. eval() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoaderMethod/EvalLoader.php:25

(Taken from: https://travis-ci.org/WyriHaximus/reactphp-inspector-stream/jobs/440604815#L636)

WyriHaximus avatar Oct 12 '18 13:10 WyriHaximus

@WyriHaximus can you look a few stack frames deeper?

PHP chops off everything from the autoloader downwards, so it is possible that a few stack frames deeper there is an unexpected autoloader kicking in.

Also, which class is being requested in this frame?

PHP  21. class_exists() /home/travis/build/WyriHaximus/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:134

Ocramius avatar Oct 15 '18 11:10 Ocramius

@Ocramius that's all that is outputted, but I'll dig into that later tonight and let it output a print backtrace plus get you that class it is trying to load

WyriHaximus avatar Oct 15 '18 16:10 WyriHaximus

Yes, what I mean is that there's surely a deeper stack frame that is below PHP's internal class_exists() call

On Mon, 15 Oct 2018, 18:13 Cees-Jan Kiewiet, [email protected] wrote:

@Ocramius https://github.com/Ocramius that's all that is outputted, but I'll dig into that later tonight and let it output a print backtrace plus get you that class it is trying to load

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Roave/BetterReflection/issues/449#issuecomment-429917333, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJakEa0rutiXDKp8MpxzvWNkDlJYL5Bks5ulLQpgaJpZM4XZf6A .

Ocramius avatar Oct 15 '18 16:10 Ocramius

Well this gets more interesting, so I prepended class_exists() with echo $className, PHP_EOL; to find out which classes get autoloaded.

This is the full out put, note the first and the last line. When reflecting a second attempt to autoload the same class, which is already monkey patched because we always load bootstrap.php at https://github.com/WyriHaximus/reactphp-inspector-stream/pull/1/files#diff-b5d0ee8c97c7abd7e3fa29b9a27d1780R29 which then monkey patches a bunch of classes: https://github.com/WyriHaximus/reactphp-inspector-stream/pull/1/files#diff-f7370275d016e437a036425d9f03ae84 Now for some reason when when attempting to reflect at https://github.com/WyriHaximus/reactphp-inspector-stream/pull/1/files#diff-48a8ed9b2290094245248b22e2de4f4cR14 it triggers the autoload which tries to autoload it again giving us the initial/current stack trace.

React\Stream\WritableResourceStream
Evenement\EventEmitter
Evenement\EventEmitterInterface
React\Stream\WritableStreamInterface
Evenement\EventEmitterTrait
React\Stream\ReadableResourceStream
Evenement\EventEmitter
Evenement\EventEmitterInterface
React\Stream\ReadableStreamInterface
Evenement\EventEmitterTrait
React\Stream\DuplexResourceStream
Evenement\EventEmitter
Evenement\EventEmitterInterface
React\Stream\DuplexStreamInterface
React\Stream\ReadableStreamInterface
React\Stream\WritableStreamInterface
Evenement\EventEmitterTrait
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

...PHP Fatal error:  Class 'Evenement\EventEmitter' not found in /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoaderMethod/EvalLoader.php(25) : eval()'d code on line 3
PHP Stack trace:
PHP   1. {main}() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/phpunit:0
PHP   2. PHPUnit\TextUI\Command::main() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/phpunit:53
PHP   3. PHPUnit\TextUI\Command->run() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/Command.php:148
PHP   4. PHPUnit\TextUI\TestRunner->doRun() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/Command.php:195
PHP   5. PHPUnit\Framework\TestSuite->run() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:545
PHP   6. PHPUnit\Framework\TestSuite->run() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestSuite.php:755
PHP   7. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->run() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestSuite.php:755
PHP   8. PHPUnit\Framework\TestResult->run() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:894
PHP   9. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->runBare() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestResult.php:698
PHP  10. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->runTest() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:939
PHP  11. ReflectionMethod->invokeArgs() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:1071
PHP  12. WyriHaximus\React\Tests\Inspector\Stream\MonkeyTest->testRerouting() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/phpunit/phpunit/src/Framework/TestCase.php:1071
PHP  13. WyriHaximus\React\Inspector\Stream\Monky::patch() /home/wyrihaximus/Projects/reactphp-inspector-stream/tests/MonkeyTest.php:14
PHP  14. Roave\BetterReflection\Reflector\ClassReflector->reflect() /home/wyrihaximus/Projects/reactphp-inspector-stream/src/Monky.php:14
PHP  15. Roave\BetterReflection\SourceLocator\Type\MemoizingSourceLocator->locateIdentifier() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/Reflector/ClassReflector.php:35
PHP  16. Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator->locateIdentifier() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/MemoizingSourceLocator.php:40
PHP  17. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->locateIdentifier() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AggregateSourceLocator.php:38
PHP  18. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->createLocatedSource() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AbstractSourceLocator.php:42
PHP  19. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->attemptAutoloadForIdentifier() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:71
PHP  20. Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator->locateClassByName() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:91
PHP  21. class_exists() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:135
PHP  22. spl_autoload_call() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:135
PHP  23. Roave\BetterReflection\Util\Autoload\ClassLoader->__invoke() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/SourceLocator/Type/AutoloadSourceLocator.php:135
PHP  24. Roave\BetterReflection\Util\Autoload\ClassLoaderMethod\EvalLoader->__invoke() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoader.php:58
PHP  25. eval() /home/wyrihaximus/Projects/reactphp-inspector-stream/vendor/roave/better-reflection/src/Util/Autoload/ClassLoaderMethod/EvalLoader.php:25
React\Stream\WritableResourceStream

WyriHaximus avatar Oct 15 '18 20:10 WyriHaximus

What's your source locator setup?

Ocramius avatar Oct 15 '18 20:10 Ocramius

The default

WyriHaximus avatar Oct 15 '18 20:10 WyriHaximus

Interesting, the monkey patched classes don't show up in get_declared_classes()

WyriHaximus avatar Oct 15 '18 20:10 WyriHaximus

Ok, so what's happening (in my opinion, not 100% sure) is following.

  1. class A depends on class B
  2. while attempting to find A, it falls back to the AutoloadSourceLocator
  3. the AutoloadSourceLocator replaces the filesystem stream wrapper, finds A, plus a bunch of other things related to it (this is where I don't know WHAT other things).
  4. the filesystem stream wrapper is not replaced, and hence any further autoloading fails (this is where you currently are). The EvalLoader kicks in, although no autoloading should've happened in first place.

In order to understand what's happening I'd need you to intercept that last autoload call, and then see how deep you are (well below stack frame 25)

Ocramius avatar Oct 15 '18 20:10 Ocramius

Right so I put var_export(get_declared_classes()); at the end of my bootstrap.php and none of the monkey patched classes where listed. They have been patched at this point tho, simple not used.

But when putting the following just above that line they are listed, loaded, and all tests pass without any issue.

new ReadableResourceStream(STDIN, Factory::create());
new WritableResourceStream(STDOUT, Factory::create());
new DuplexResourceStream(fopen('php://memory', 'w+'), Factory::create());

In the mean time I've added the following fix: https://github.com/WyriHaximus/reactphp-inspector-stream/pull/1/commits/9964f2bc4e28d6bf63d79bd80ce8162aaf9e0d92, and while that resolves my issues, I can spent more time on this and try to figure out what is making it consider those classes not loaded. Especially looking at: https://3v4l.org/g3Wam

WyriHaximus avatar Oct 15 '18 21:10 WyriHaximus

Note; may help to understand that the classes aren't loaded until used; the monkey patching system is an autoloader that sits on top, and indeed when you use the classes, the patched versions are loaded (hence why the classes must not be loaded beforehand, and why our autoloader must be the "last"). As soon as the class is loaded into PHP, it can't be changed (unless you use something else like runkit)...

asgrim avatar Oct 16 '18 08:10 asgrim

Monkey-patching has been removed in 6.0.0: closing here.

Ocramius avatar Oct 18 '22 05:10 Ocramius

Monkey-patching has been removed in 6.0.0: closing here.

Thanks for letting me know, completely missed that in 6.0 😅

WyriHaximus avatar Oct 22 '22 14:10 WyriHaximus