Giter VIP home page Giter VIP logo

runkit7's Introduction

Runkit7: Independent fork of runkit for PHP 7.2+

For all those things you.... probably shouldn't have been doing anyway.... but surely do! Supports PHP7.2-8.1! (function/method manipulation is recommended only for unit testing, but all other functionality works.)

  • Function/method manipulation crashes in PHP 7.4+ when opcache is enabled (e.g. opcache.enable_cli) (Issue #217)

    Disabling opcache is the recommended workaround.

  • This has been tested with php 8.2.0beta1

Build Status Build Status (Windows)

Building and installing runkit7 in unix

Building and installing runkit7 in Windows

This extension's documentation is available at https://www.php.net/runkit7.

Compatibility: PHP7.2 to PHP 8.1

See runkit-api.php for the implemented functionality and method signatures. New functionality was added to support usage with PHP7.

  • This adds the ability to set return types (including nullable return types) on added/redefined functions.
  • This adds the ability to set declare(strict_types=1) on added/redefined functions.

Superglobals work reliably when tested on web servers and tests. Class and function manipulation is recommended only for unit tests.

  • The runkit.superglobal ini setting works reliably in PHP 7.

  • Manipulating user-defined (i.e. not builtin or part of extensions) functions and methods via runkit7_method_* and runkit7_function_* generally works, but is recommended only in unit tests (unlikely to crash, but will cause memory leaks)

  • Manipulating built in functions may cause segmentation faults in rare cases. File a bug report if you see this. Function and method manipulation is recommended only for debugging or unit tests, because of the possibility of crashes. (Manipulating built in class methods is impossible/not supported)

  • Adding default properties to classes doesn't work in php7, because of a change in the way PHP stores objects. Eventually, I plan to add runkit_default_property_modify, which will replace one default value with a different default property, keeping the number of properties the same. See the reasons for disabling property manipulation at PROPERTY_MANIPULATION.md As a substitute, user code can do the following things:

    • rename (or add) __construct with runkit7_method_rename/runkit7_method_add, and create a new version of __construct that sets the properties, then calls the original constructor.
    • For getting/setting properties of individual objects, see ReflectionProperty ReflectionProperty->setAccessible(true) and ReflectionProperty->setValue(), etc.
  • Modifying constants works for constants declared in different files, but does not work for constants within the same file. PHP7 inlines constants within the same file if they are guaranteed to have only one definition. Patching php-src and/or opcache to not inline constants (e.g. based on a php.ini setting) is possible, but hasn't been tried yet.

  • Sandboxing (and runkit_lint) were removed.

The following contributions are welcome:

  • Pull requests with PHP5 -> PHP7 code migration of functions
  • New test cases for features that no longer work in PHP7, or bug reports containing code crashing runkit7.
  • Issues for PHP language features that worked in PHP5, but no longer work in PHP7, for the implemented methods (runkit7_function_* and runkit7_method_*)
  • Fixes and documentation.

Other methods and corresponding tests are disabled/skipped because changes to php internals in php7 made them impractical.

This is runkit7 4.x. Use runkit7 2.x for PHP 7.1 support, or 1.x for PHP 7.0 support.

Examples

The following mocking libraries work with runkit7.

  • Build Status timecop-PHP (Fork), a time testing library inspired by the ruby timecop gem (requires runkit.internal_override=1, suggested only for unit tests)
  • CI staticmock, a mockery-like DSL to replace static methods in tests.
  • Build Status SimpleStaticMock (Fork), a very simple class to mock static methods in unit tests. Unrelated to tototoshi/staticmock.
  • Build Status TraceOn (Fork), a simple PHP utility to trace(investigate) invocations of instance/static/global methods.

PHP7 SPECIFIC DETAILS

Bugs in runkit7

  • There are segmentation faults when manipulating internal functions (a.k.a. "runkit.internal_override=1") (when you rename/redefine/(copy?) internal functions, and call internal functions with user functions' implementation, or vice versa) (and when functions redefinitions aren't cleaned up) Many (but not all) of these crashes have been fixed.
  • There are reference counting bugs causing memory leaks. 2 calls to emalloc have been temporarily replaced with calls to pemalloc so that tests would not crash during shutdown (and other reasons)
  • The Zend VM bytecode representation may change in 8.0 betas and in future minor/major PHP releases after 8.0. Any new opcodes added may not work as expected with each new minor php version release.

APIs for PHP7

Implemented APIs for PHP7

NOTE: Most runkit7_*() functions have aliases of runkit_*().

  • runkit7_function_*: Most tests are passing. There are some memory leaks when renaming internal functions.
  • runkit7_method_*: Most tests are passing. There are some memory leaks when renaming internal functions.
  • runkit7_zval_inspect: Partly passing, and needs to be rewritten because of PHP7's zval changes.
  • runkit7_constant_add works. Other constant manipulation functions don't work for constants accessed within the same file due to the compiler inlining their values for performance.
  • Runkit superglobals.

Unsupported APIs for PHP7:

(These functions will be missing. Some of these should be possible to implement.)

  • runkit_class_adopt and runkit_class_emancipate Disabled because of bugs related to properties.

  • runkit7_import This had known crashes in php 7.3+, and was removed in runkit7 4.0 because they weren't straightforward to fix. Use runkit7 3.x if you need to continue using this functionality.

  • runkit_lint* Might be possible if this is rewritten to use actual threading: See issue #114

  • runkit7_constant_* : runkit7_constant_add works reliably, other methods don't. This works better when the constants are declared in a file separate from the file accessing that constant.

  • runkit_default_property_* Disabled because of bugs related to properties See issue #30 (implement function to modify only) and issue #113 (Manipulate static properties)

    runkit_default_property_add has been removed in php7 - it requires reallocing a different zval to add a property to the property table That would break a lot of things (PHP internals, other PHP modules, etc)

  • runkit_return_value_used: Removed, was not working and unrelated to other features. vld seems to have a working implementation in the opcode analyzer, not familiar with how it works.

Reasons for disabling property manipulation

See PROPERTY_MANIPULATION.md

FURTHER WORK

See https://github.com/runkit7/runkit7/issues

Tasks for the near future:

  • Replace property manipulation with runkit_default_property_modify (#30)

Contributing

See CONTRIBUTING.md for guidelines on issues and pull requests, as well as links to resources that are useful for php7 module and runkit7 development.

UPSTREAM DOCUMENTATION

(runkit7 is a fork of https://github.com/zenovich/runkit, implementing php7.2+ support)

Features

Runkit has two groups of features outlined below (Sandboxing was removed in runkit7):

CUSTOM SUPERGLOBALS

A new .ini entry runkit.superglobal is defined which may be specified as a simple variable, or list of simple variables to be registered as superglobals. runkit.superglobal is defined as PHP_INI_SYSTEM and must be set in the system-wide php.ini.

Example:

php.ini:

runkit.superglobal=foo,bar

test.php:

function testme() {
  echo "Foo is $foo\n";
  echo "Bar is $bar\n";
  echo "Baz is $baz\n";
}
$foo = 1;
$bar = 2;
$baz = 3;

testme();

Outputs:

Foo is 1
Bar is 2
Baz is

USER DEFINED FUNCTION AND CLASS MANIPULATION

NOTE: Only a subset of the APIs have been ported to PHP7. Some of these APIs have segmentation faults in corner cases (when runkit.internal_override=On)

User defined functions and user defined methods may now be renamed, delete, and redefined using the API described at http://www.php.net/runkit

Examples for these functions may also be found in the tests folder.

runkit_lint alternatives

runkit_lint was disabled with the rest of the sandbox code due to issues porting it to PHP 7 (Issue #114). As a replacement for runkit_lint/runkit_lint_file try any of the following:

  • php -l --no-php-ini $filename will quickly check if a file is syntactically valid, but will not show you any php notices about deprecated code, etc.

  • opcache_compile_file may help, but will not show you any notices.

  • token_get_all($code, TOKEN_PARSE) will detect invalid ASTs in php 7.0+

  • Projects such as PHP-Parser (Pure PHP) and php-ast (C module), which produce an Abstract Syntax Tree from php code. Alternately, token_get_all() will throw an error for syntax errors if the flag TOKEN_PARSE is passed in. (Unfortunately, it parses but does not detect erroneous code, e.g. duplicate classes/methods in the same file).

    // Example replacement for runkit_lint.
    try {
        $ast = token_get_all('<?php function foo(){}', TOKEN_PARSE)
        return true;
    } catch (ParseError $e) {
        return false;
    }

    Alternately, you may wish to use a different approach and run a PHP static analyzer such as Phan, Psalm, or PHPStan

Installation

BUILDING AND INSTALLING RUNKIT7 IN UNIX

pecl install runkit7 can be used to install runkit7 releases published on PECL.

Tarballs can be downloaded from PECL or GitHub.

An example of how to build the latest master branch from source is below:

git clone https://github.com/runkit7/runkit7.git
cd runkit7
phpize
# The sandbox related code and flags have been removed, no need to disable them.
# (--enable-runkit7-modify (on by default) controls function, method, class, manipulation, and will control property manipulation)
# (--enable-runkit7-super (on by default) allows you to add custom superglobals)
# ./configure --help lists available configuration options.
./configure
make
make test
sudo make install

BUILDING AND INSTALLING RUNKIT7 IN WINDOWS

Setting up php build environment

Read https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2 and https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2#building_pecl_extensions first. This is just a special case of these instructions.

For PHP7.2+, you need to install "Visual Studio 2017 Community Edition" (or other 2017 edition). For PHP8.0+, you need to install "Visual Studio 2019 Community Edition" (or other 2019 edition). Make sure that C++ is installed with Visual Studio. The command prompt to use is "VS2017 x86 Native Tools Command Prompt" on 32-bit, "VS2017 x64 Native Tools Command Prompt" on 64-bit.

  • Note that different visual studio versions are needed for different PHP versions. For PHP 8.0+, use Visual Studio 2019 and vs16 instead.

For 64-bit installations of php7, use "x64" instead of "x86" for the below commands/folders.

After completing setup steps mentioned, including for C:\php-sdk\phpdev\vc14

extract download of php-7.4.11-src (or any version of php 7) to C:\php-sdk\phpdev\vc15\x86\php-7.4.11-src

Installing runkit7 on windows

There are currently no sources providing DLLs of this fork. Runkit7 and other extensions used must be built from source.

Create subdirectory C:\php-sdk\phpdev\vc14\x86\pecl, adjacent to php source directory)

extract download of runkit7 to C:\php-sdk\phpdev\vc14\x86\pecl\runkit7 (all of the c files and h files should be within runkit7)

Then, execute the following (Add --enable-runkit7 to the configure flags you were already using)

cd C:\php-sdk
C:\php-sdk\bin\phpsdk_setvars.bat
cd phpdev\vc15\x86\php-7.4.11\src
buildconf
configure --enable-runkit7
nmake

Then, optionally test it (Most of the tests should pass, be skipped, or be known failures.):

nmake test TESTS="C:\php-sdk\vc14\x86\pecl\runkit7\tests

runkit7's People

Contributors

davidsteinsland avatar egifford avatar nateabele avatar remicollet avatar sgolemon avatar tony2001 avatar tototoshi avatar tysonandre avatar tysonandre-continuousintegration avatar tysonandre-tmg avatar zenovich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

runkit7's Issues

Add unit tests of features added in 7.1 (e.g. Closure::fromCallable)

  • New optional param types, optional return types (Probably not going to require changes)
  • Closure::fromCallable - May require changes (e.g. Closure::fromCallable('foo') should be affected by runkit operations on 'foo')
  • runkit_function_* and runkit_method_* where source is a Closure
  • iterable type, nullable types,

Add travis CI integration for php7 minor versions and nightly builds

The goal is to add integration tests for commits, and a build status in the README. The travis config would be similar to the travis config for the project phalcon/cphalcon

Travis's binaries are built with these config flags :
https://github.com/travis-ci/travis-cookbooks/blob/precise-stable/ci_environment/phpbuild/templates/default/default_configure_options.erb

Also useful to test with/without xdebug, without --enable-maintainer-zts

https://docs.travis-ci.com/user/languages/php#PHP-extensions

Add runkit.php stub file for documentation of API changes

New parameters will be added, e.g. for return types

Different methods will be added, e.g. for modifying (not adding/removing) default properties)

Document known bugs in methods that can't be fixed (E.g. aggressive php inlining of constants).

(PHP 7.1) Allow manipulating constant visibility

Currently, all redefined constants have the default public visibility.

For php 7.1 builds, add another parameter to runkit_constant_redefine/add after the rest of the parameters (int $visibility = RUNKIT_ACC_PUBLIC|RUNKIT_ACC_PROTECTED|RUNKIT_ACC_PRIVATE)

  • runkit_constant_redefine should preserve existing visibility by default
  • runkit_constant_add should make visibility public by default.

Re-enable runkit_import, supporting a subset of the flags

This was temporarily disabled because it depended on redefining properties of objects (instances), which was impractical in php 7.0+.
However, importing classes and functions which don't already exist should be possible. The code hasn't yet been ported to compile with the php 7.0 data structures and APIs, and may have to be restored from git.

If RUNKIT_IMPORT_OVERRIDE is enabled, then when combined with the below flags:

  • RUNKIT_IMPORT_CLASS_PROPS - Likely not possible if instances already exist. (By extension, RUNKIT_IMPORT_CLASSES isn't possible)
  • RUNKIT_IMPORT_CLASS_STATIC_PROPS - Have to investigate how the PHP interpreter handles accesses to static properties --- It's similar to instance properties. The property names are converted to indices, and C code knowing the index can quickly access those properties(for internal functions).
  • RUNKIT_IMPORT_FUNCTIONS, RUNKIT_IMPORT_CLASS_METHODS, RUNKIT_IMPORT_CLASS_CONSTS: doable?

If RUNKIT_IMPORT_OVERRIDE is disabled, then any of the other flags can probably be used

  • But in the case with no overrides, one may be able to do something similar using PHP-Parser and php-ast to extract only the top-level class/function definitions.

See #72

EDIT: earlier description mixed up constants.

Automatically trigger garbage collection when adding/redefining functions

Unlike other extensions, runkit's performance for runkit_*_add/redefine gets worse if there are a large number of objects in use.

Consider adding an option to automatically trigger garbage collection based on these factors, whenever runkit_*_add/redefine is called:

Finish support for defining/undefining resource constants

  • Forbid class constants as resources but allow global constants - That's impossible for regular php code, so the interpreter may reject that now/in the future.
  • Properly free resources when calling runkit_constant_remove.

This is done in runkit_constants.c, search for IS_RESOURCE

Long term plan: Track what will happen in future PHP releases

PHP 7.2 supports the same features as PHP 7.1. However, php 8 may be different

Supposedly, a future release of PHP (8.0?) will have a JIT compiler. See http://news.php.net/php.internals/95531

  • If function inlining within a file (e.g. for private or final methods) becomes part of a future release, then document this limitation.
  • If a new opcode format is infeasible to work with (e.g. to clear cache), document this
  • If it's possible to disable optimizations in order to make runkit7 continue working, document how this will be done

Add API to perform function manipulation in bulk

Clearing the runtime cache of all functions is slow. This can be sped up slightly by deferring cache clearing, if multiple functions are renamed, redefined, or removed in the same batch

Two ways of implementing this seem possible. The 2nd one avoids certain ways it could crash.

  1. runkit_manipulate_batch(function() { runkit_method_rename..., runkit_method_add..., runkit_method_rename..., runkit_method_add...});
  2. runkit_manipulate_batch([[['runkit_method_rename', ...$args], ['runkit_method_add', ...$args]], [['runkit_method_rename', ...$args], ['runkit_method_add', ...$args]]]
    (This would return an array of arrays, corresponding to whether or not each step succeeded.)
    (If one of the steps in a list failed (returned false), then don't execute the remaining ones (and treat those as having failed). Multiple lists can be provided.)

Fix issues where php VM uses the old, incorrect stack size

When PHP7 functions are called, there are three phases: 1. initializing, 2. passing arguments, and 3. calling the function.

During the initialization phase, the amount of stack space needed by the called function is determined and allocated

In PHP7, new opcodes were introduced, which would precalculate the amount of stack space needed, instead of calculating it every time. This makes the PHP VM faster, but causes runkit redefined functions to now sometimes not have enough stack space (Depends on the number of arguments being passed, and the number of temporary variables)

INIT_FCALL_SPEC_CONST_HANDLER will have the precomputed stack size in opline.op1.result

 /* FOR ZEND_INIT_FCALL_SPEC_CONST_HANDLER */
// Add a stack frame with opline->op1.num bytes of memory
call = zend_vm_stack_push_call_frame_ex(
               opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
               fbc, opline->extended_value, NULL, NULL);
       call->prev_execute_data = EX(call);
       EX(call) = call;
/* FOR ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER */
// Add a stack frame with a dynamically calculated number of bytes of memory.
call = zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION,
    fbc, opline->extended_value, NULL, NULL);
call->prev_execute_data = EX(call);
EX(call) = call; 


// FOR ZEND_INIT_DYNAMIC_CALL_SPEC_CONST_HANDLER
// NOTE: there are others as well.
// call_info has a collection of bit flags. The calculation of the stack frame size is based on fbc (zend_function*).
call = zend_vm_stack_push_call_frame(call_info,
        fbc, opline->extended_value, called_scope, object);
call->prev_execute_data = EX(call);
EX(call) = call;

Solutions:

  1. Iterate through all method, function, closure definitions. Replace all opcodes of type ZEND_INIT_FCALL_SPEC_CONST_HANDLER (and the case insensitive modified function name) with the less optimized ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER (May be infeasible and possibly break with some opcache options), and so on
  2. In runkit_function_redefine/add, create extremely simple methods which have no temporary variables and call the provided function definition with the expected func_num_args() (i.e. add a second stack frame)

Branch try-support-internal-functions-v2 contains notes on this (and work on fixing internal method modification)
tests/runkit_function_redefine_var_dump.phpt is failing because of this issue.

runkit_function_redefine not working with internal functions

runkit_function_redefine doesn't affect internal functions.
the following code:

<?php
function testme() {
  echo strlen("Original Testme Implementation")."\n";
}
testme();
runkit_function_redefine('strlen','$string','return "5";');
testme();

Gives this result:

30
30

This is the result with internal_override off:

30
PHP Warning:  runkit_function_redefine(): strlen() is an internal function and runkit.internal_override is disabled in 2run.php on line 6
30

runkit_function_redefine doesn't work either (Except for other runkit related functions):

<?php
runkit_function_remove('strlen');
$var='some string to be measured';
echo "The length of '{$var}' is  ".strlen($var)."\n";

returns The length of 'some string to be measured' is 26

PHP version:

PHP 7.0.10-1~dotdeb+8.1 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

Regards.

Handle `declare(strict_types=1)`, allow creating mocked code as if this option was true or false

And add tests to enforce this behavior

  • Is it always going to be false? Investigate how the PHP engine know if a given zend_function is strict.
  • Add an optional ?bool to the argument lists to add/redefine methods, make this set the strictness, if possible
  • When eval()ing code to create a new zend_function, set this directive at the top.
  • Also figure out how to handle copying the contents of a closure directly into a added function/method.

Support for 'unset(MyClass::class);' ?

We're using a plugin-architecture here for a running php websocket server. Each plugin providing a small subset of features. However the lack of unloading our plugin classes, updating them, and reloading the updated ones is resulting in the silly requirement to halt the whole application to avoid "class already exists" errors, which causes all open connections to get closed. So in theory all we need is unset(MyClass::class); to let the autoloader re-do its magic. As this is the only functionality we need, we could accept if all other methods are disabled due to buggy behavior.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.