staabm / side-effects-detector
A static analysis tool to detect side effects in PHP code
Fund package maintenance!
staabm
Installs: 41 419
Dependents: 1
Suggesters: 0
Security: 0
Stars: 29
Watchers: 2
Forks: 0
Open Issues: 5
Requires
- php: ^7.4 || ^8.0
- ext-tokenizer: *
Requires (Dev)
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^1.12.6
- phpunit/phpunit: ^9.6.21
- symfony/var-dumper: ^5.4.43
- tomasvotruba/type-coverage: 1.0.0
- tomasvotruba/unused-public: 1.0.0
This package is auto-updated.
Last update: 2024-11-04 11:10:17 UTC
README
Analyzes php-code for side-effects.
When code has no side-effects it can e.g. be used with eval($code)
in the same process without interfering.
Side-effects are classified into categories to filter them more easily depending on your use-case.
This library is used e.g. in PHPUnit to improve performance of PHPT test-cases.
Install
composer require staabm/side-effects-detector
Usage
Example:
use staabm\SideEffectsDetector\SideEffectsDetector; $code = '<?php version_compare(PHP_VERSION, "8.0", ">=") or echo("skip because attributes are only available since PHP 8.0");'; $detector = new SideEffectsDetector(); // [SideEffect::STANDARD_OUTPUT] var_dump($detector->getSideEffects($code));
In case functions are called which are not known to have side-effects - e.g. userland functions - null
is returned.
use staabm\SideEffectsDetector\SideEffectsDetector; $code = '<?php userlandFunction();'; $detector = new SideEffectsDetector(); // [SideEffect::MAYBE] var_dump($detector->getSideEffects($code));
Code might have multiple side-effects:
use staabm\SideEffectsDetector\SideEffectsDetector; $code = '<?php include "some-file.php"; echo "hello world"; exit(1);'; $detector = new SideEffectsDetector(); // [SideEffect::SCOPE_POLLUTION, SideEffect::STANDARD_OUTPUT, SideEffect::PROCESS_EXIT] var_dump($detector->getSideEffects($code));
Compensate some side-effects
It might be useful to compensate some side-effects, so evaluation of code in the current process is still acceptable:
use staabm\SideEffectsDetector\SideEffectsDetector; function runCodeInLocalSandbox(string $code): string { $code = preg_replace('/^<\?(?:php)?|\?>\s*+$/', '', $code); $code = preg_replace('/declare\S?\([^)]+\)\S?;/', '', $code); // wrap in immediately invoked function to isolate local-side-effects // of $code from our own process $code = '(function() {' . $code . '})();'; // wrap in output buffer to isolate stdout side-effects ob_start(); @eval($code); return ob_get_clean(); } function shouldRunInSubprocess(string $code): bool { $detector = new SideEffectsDetector; $sideEffects = $detector->getSideEffects($code); if ($sideEffects === []) { return false; // no side-effects } foreach ($sideEffects as $sideEffect) { // stdout is fine, we will catch it using output-buffering if ($sideEffect === SideEffect::STANDARD_OUTPUT) { continue; } return true; } return false; } function runCode(string $code) { if (!shouldRunInSubprocess($code)) { return runCodeInLocalSandbox($code); } // run $code in isolation, e.g. in a subprocess // ... }
Disclaimer
Non goals are:
- find the best possible answer for all cases
- add runtime dependencies
- inspect additional metadata like attributes or phpdoc tags
If you are in need of a fully fledged side-effect analysis, use more advanced tools like PHPStan.
Look at the test-suite to get an idea of supported use-cases.