Skip to content

Commit

Permalink
Merge pull request #60 from antecedent/interception-of-language-const…
Browse files Browse the repository at this point in the history
…ructs

Make language constructs (print, eval, clone etc.) redefinable
  • Loading branch information
antecedent authored May 22, 2017
2 parents 5e4530a + 0ea5d74 commit efac55c
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 3 deletions.
6 changes: 6 additions & 0 deletions Patchwork.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ function always($value)
CallRerouting\createStubsForInternals();
CallRerouting\connectDefaultInternals();

require __DIR__ . '/src/Redefinitions/LanguageConstructs.php';

CodeManipulation\register([
CodeManipulation\Actions\RedefinitionOfLanguageConstructs\spliceAllConfiguredLanguageConstructs(),
]);

if (Utils\wasRunAsConsoleApp()) {
require __DIR__ . '/src/Console.php';
}
10 changes: 8 additions & 2 deletions src/CallRerouting.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Patchwork\Stack;
use Patchwork\Config;
use Patchwork\Exceptions;
use Patchwork\CodeManipulation\Actions\RedefinitionOfLanguageConstructs;

const INTERNAL_REDEFINITION_NAMESPACE = 'Patchwork\Redefinitions';
const EVALUATED_CODE_FILE_NAME_SUFFIX = '/\(\d+\) : eval\(\)\'d code$/';
Expand All @@ -33,6 +34,7 @@ function @name(@signature) {

function connect($source, callable $target, Handle $handle = null, $partOfWildcard = false)
{
$source = Utils\translateCallable($source);
$handle = $handle ?: new Handle;
list($class, $method) = Utils\interpretCallable($source);
if (constitutesWildcard($source)) {
Expand Down Expand Up @@ -233,7 +235,9 @@ function dispatchTo(callable $target)

function dispatch($class, $calledClass, $method, $frame, &$result, array $args = null)
{
if (strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0 && $args === null) {
$isInternalStub = strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0;
$isLanguageConstructStub = strpos($method, RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX) === 0;
if ($isInternalStub && !$isLanguageConstructStub && $args === null) {
# Mind the namespace-of-origin argument
$trace = debug_backtrace();
$args = array_reverse($trace)[$frame - 1]['args'];
Expand Down Expand Up @@ -273,7 +277,9 @@ function relay(array $args = null)
if ($args === null) {
$args = $top['args'];
}
if (strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0) {
$isInternalStub = strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0;
$isLanguageConstructStub = strpos($method, RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX) === 0;
if ($isInternalStub && !$isLanguageConstructStub) {
array_unshift($args, '');
}
try {
Expand Down
1 change: 1 addition & 0 deletions src/CodeManipulation.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require __DIR__ . '/CodeManipulation/Actions/CodeManipulation.php';
require __DIR__ . '/CodeManipulation/Actions/Namespaces.php';
require __DIR__ . '/CodeManipulation/Actions/RedefinitionOfInternals.php';
require __DIR__ . '/CodeManipulation/Actions/RedefinitionOfLanguageConstructs.php';
require __DIR__ . '/CodeManipulation/Actions/ConflictPrevention.php';

use Patchwork\Exceptions;
Expand Down
121 changes: 121 additions & 0 deletions src/CodeManipulation/Actions/RedefinitionOfLanguageConstructs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/**
* @link http://patchwork2.org/
* @author Ignas Rudaitis <ignas.rudaitis@gmail.com>
* @copyright 2010-2017 Ignas Rudaitis
* @license http://www.opensource.org/licenses/mit-license.html
*/
namespace Patchwork\CodeManipulation\Actions\RedefinitionOfLanguageConstructs;

use Patchwork\CodeManipulation\Source;
use Patchwork\CodeManipulation\Actions\Generic;
use Patchwork\Exceptions;
use Patchwork\Config;

const LANGUAGE_CONSTRUCT_PREFIX = 'Patchwork\Redefinitions\LanguageConstructs\_';

/**
* @since 2.0.5
*/
function spliceAllConfiguredLanguageConstructs()
{
$mapping = getMappingOfConstructs();
$used = [];
$actions = [];
foreach (Config\getRedefinableLanguageConstructs() as $construct) {
if (isset($used[$mapping[$construct]])) {
continue;
}
$used[$mapping[$construct]] = true;
$actions[] = spliceLanguageConstruct($mapping[$construct]);
}
return Generic\chain($actions);
}

function getMappingOfConstructs()
{
return [
'echo' => T_ECHO,
'print' => T_PRINT,
'eval' => T_EVAL,
'die' => T_EXIT,
'exit' => T_EXIT,
'isset' => T_ISSET,
'unset' => T_UNSET,
'empty' => T_EMPTY,
'require' => T_REQUIRE,
'require_once' => T_REQUIRE_ONCE,
'include' => T_INCLUDE,
'include_once' => T_INCLUDE_ONCE,
'clone' => T_CLONE,
];
}

function getInnerTokens()
{
return [
'$',
',',
T_OBJECT_OPERATOR,
T_DOUBLE_COLON,
T_NS_SEPARATOR,
T_STRING,
T_LNUMBER,
T_DNUMBER,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_COMMENT,
T_DOC_COMMENT,
];
}

function getBracketTokens()
{
return [
Generic\LEFT_ROUND,
Generic\LEFT_SQUARE,
Generic\LEFT_CURLY,
T_CURLY_OPEN,
];
}

function spliceLanguageConstruct($token)
{
return function(Source $s) use ($token) {
foreach ($s->all($token) as $pos) {
$s->splice('\\' . LANGUAGE_CONSTRUCT_PREFIX, $pos, 0, Source::PREPEND);
if (lacksParentheses($s, $pos)) {
addParentheses($s, $pos);
}
}
};
}

function lacksParentheses(Source $s, $pos)
{
if ($s->is(T_ECHO, $pos)) {
return true;
}
$next = $s->skip(Source::junk(), $pos);
return !$s->is(Generic\LEFT_ROUND, $next);
}

function addParentheses(Source $s, $pos)
{
$pos = $s->skip(Source::junk(), $pos);
$s->splice(Generic\LEFT_ROUND, $pos, 0, Source::PREPEND);
while ($pos < count($s->tokens)) {
if ($s->is(getInnerTokens(), $pos)) {
$pos++;
} elseif ($s->is(getBracketTokens(), $pos)) {
$pos = $s->match($pos) + 1;
} else {
break;
}
}
if ($s->is(Source::junk(), $pos)) {
$pos = $s->skipBack(Source::junk(), $pos);
}
$s->splice(Generic\RIGHT_ROUND, $pos, 0, Source::APPEND);
}
11 changes: 11 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use Patchwork\Utils;
use Patchwork\Exceptions;
use Patchwork\CodeManipulation\Actions\RedefinitionOfLanguageConstructs;

const FILE_NAME = 'patchwork.json';

Expand Down Expand Up @@ -153,6 +154,15 @@ function getRedefinableInternals()
function setRedefinableInternals($names)
{
merge(State::$redefinableInternals, $names);
$allConstructs = array_keys(RedefinitionOfLanguageConstructs\getMappingOfConstructs());
$constructs = array_intersect(State::$redefinableInternals, $allConstructs);
State::$redefinableLanguageConstructs = array_merge(State::$redefinableLanguageConstructs, $constructs);
State::$redefinableInternals = array_diff(State::$redefinableInternals, $constructs);
}

function getRedefinableLanguageConstructs()
{
return State::$redefinableLanguageConstructs;
}

function getCachePath()
Expand Down Expand Up @@ -199,5 +209,6 @@ class State
static $whitelist = [];
static $cachePath;
static $redefinableInternals = [];
static $redefinableLanguageConstructs = [];
static $timestamp = 0;
}
76 changes: 76 additions & 0 deletions src/Redefinitions/LanguageConstructs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/**
* @link http://patchwork2.org/
* @author Ignas Rudaitis <ignas.rudaitis@gmail.com>
* @copyright 2010-2017 Ignas Rudaitis
* @license http://www.opensource.org/licenses/mit-license.html
*/
namespace Patchwork\Redefinitions\LanguageConstructs;

function _echo($string)
{
foreach (func_get_args() as $argument) {
echo $argument;
}
}

function _print($string)
{
return print($string);
}

function _eval($code)
{
return eval($code);
}

function _die($message = null)
{
die($message);
}

function _exit($message = null)
{
exit($message);
}

function _isset(&$lvalue)
{
return isset($lvalue);
}

function _unset(&$lvalue)
{
unset($lvalue);
}

function _empty(&$lvalue)
{
return empty($lvalue);
}

function _require($path)
{
return require($path);
}

function _require_once($path)
{
return require_once($path);
}

function _include($path)
{
return include($path);
}

function _include_once($path)
{
return include_once($path);
}

function _clone($object)
{
return clone $object;
}
20 changes: 20 additions & 0 deletions src/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use Patchwork\Config;
use Patchwork\CallRerouting;
use Patchwork\CodeManipulation;

const ALIASING_CODE = '
namespace %s;
Expand Down Expand Up @@ -224,6 +225,25 @@ function getRedefinableCallables()
return array_merge(getUserDefinedCallables(), Config\getRedefinableInternals());
}

/**
* @since 2.0.5
*
* As of version 2.0.5, this is used to accommodate language constructs
* (echo, eval, exit and others) within the concept of callable.
*/
function translateCallable($callable)
{
if (!is_string($callable)) {
return $callable;
}
$constructs = Config\getRedefinableLanguageConstructs();
if (in_array($callable, $constructs)) {
return CodeManipulation\Actions\RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX . $callable;
} else {
return $callable;
}
}

function getUserDefinedMethods()
{
static $result = [];
Expand Down
9 changes: 9 additions & 0 deletions tests/includes/LanguageConstructUsages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

echo 'This is a string';
echo 'This is a string', ' and this is another one';
print(404);
exit('error');
die();
die;
false or die;
65 changes: 65 additions & 0 deletions tests/language-constructs.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
Redefining language constructs like die(), echo, require_once etc. (https://github.com/antecedent/patchwork/issues/59)

--SKIPIF--
<?php !defined('HHVM_VERSION')
or die('skip because the redefinition of language constructs is not yet implemented for HHVM') ?>

--FILE--
<?php

namespace Patchwork;

assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_WARNING, 1);
error_reporting(E_ALL | E_STRICT);

$_SERVER['PHP_SELF'] = __FILE__;

require __DIR__ . "/../Patchwork.php";

$calls = [
'die' => [],
'exit' => [],
'echo' => [],
'print' => [],
];

function collect(array &$collection)
{
return function() use (&$collection) {
$collection[] = func_get_args();
};
}

redefine('die', collect($calls['die']));
redefine('exit', collect($calls['exit']));
redefine('echo', collect($calls['echo']));
redefine('print', collect($calls['print']));

require __DIR__ . "/includes/LanguageConstructUsages.php";

assert($calls['die'] == [
[],
[],
[],
]);

assert($calls['exit'] == [
['error'],
]);

assert($calls['echo'] == [
['This is a string'],
['This is a string', ' and this is another one'],
]);

assert($calls['print'] == [
[404],
]);

?>
===DONE===

--EXPECT--
===DONE===
Loading

0 comments on commit efac55c

Please sign in to comment.