Skip to content

Commit

Permalink
Whitelist global functions by default
Browse files Browse the repository at this point in the history
Closes #226
  • Loading branch information
theofidry committed Jun 30, 2018
1 parent 1bfe0d0 commit 8a8736a
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 15 deletions.
8 changes: 7 additions & 1 deletion fixtures/set024/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

namespace Acme;

require_once __DIR__ . '/vendor/autoload.php';
use function file_exists;

if (file_exists($autoload = __DIR__ . '/vendor/scoper-autoload.php')) {
require_once $autoload;
} else {
require_once __DIR__ . '/vendor/autoload.php';
}

dump('yo');
1 change: 0 additions & 1 deletion scoper.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Isolated\Symfony\Component\Finder\Finder;

return [
'prefix' => 'Foo',
'whitelist' => [
Finder::class,
],
Expand Down
42 changes: 38 additions & 4 deletions src/Autoload/ScoperAutoloadGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@

namespace Humbug\PhpScoper\Autoload;

use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UserGlobalFunctionCollection;
use Humbug\PhpScoper\Whitelist;
use function array_map;
use function iterator_to_array;
use const PHP_EOL;
use PhpParser\Node\Name\FullyQualified;
use function sprintf;

final class ScoperAutoloadGenerator
{
Expand All @@ -28,9 +33,8 @@ public function __construct(Whitelist $whitelist)

public function dump(string $prefix): string
{
$statements = $this->createStatements($prefix);

$statements = implode(PHP_EOL, $statements);
$statements = implode(PHP_EOL, $this->createClassAliasStatements($prefix)).PHP_EOL;
$statements .= implode(PHP_EOL, $this->createFunctionAliasStatements($this->whitelist->getUserGlobalFunctions()));

return <<<PHP
<?php
Expand All @@ -49,7 +53,7 @@ public function dump(string $prefix): string
/**
* @return string[]
*/
public function createStatements(string $prefix): array
public function createClassAliasStatements(string $prefix): array
{
return array_map(
function (string $whitelistedElement) use ($prefix): string {
Expand All @@ -62,4 +66,34 @@ function (string $whitelistedElement) use ($prefix): string {
$this->whitelist->getClassWhitelistArray()
);
}

/**
* @return string[]
*/
public function createFunctionAliasStatements(UserGlobalFunctionCollection $userGlobalFunctions): array
{
return array_map(
function (array $node): string {
/**
* @var FullyQualified $original
* @var FullyQualified $alias
*/
[$original, $alias] = $node;

return sprintf(
<<<'PHP'
if (!function_exists('%1$s')) {
function %1$s() {
return \%2$s(func_get_args());
}
}
PHP
,
$original->toString(),
$alias->toString()
);
},
iterator_to_array($userGlobalFunctions)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace Humbug\PhpScoper\PhpParser\NodeVisitor\Collection;

use ArrayIterator;
use function count;
use Countable;
use Humbug\PhpScoper\PhpParser\NodeVisitor\AppendParentNode;
use IteratorAggregate;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Humbug\PhpScoper\PhpParser\NodeVisitor\Collection;


use ArrayIterator;
use Countable;
use IteratorAggregate;
use PhpParser\Node\Name\FullyQualified;
use function count;

final class UserGlobalFunctionCollection implements IteratorAggregate, Countable
{
/**
* @var FullyQualified[][]
*/
private $nodes = [];

public function add(FullyQualified $original, FullyQualified $alias): void
{
$this->nodes[] = [$original, $alias];
}

/**
* @inheritdoc
*/
public function count(): int
{
return count($this->nodes);
}

/**
* @inheritdoc
*/
public function getIterator(): iterable
{
return new ArrayIterator($this->nodes);
}
}
90 changes: 90 additions & 0 deletions src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

/*
* This file is part of the humbug/php-scoper package.
*
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
* Pádraic Brady <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Humbug\PhpScoper\PhpParser\NodeVisitor;

use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UserGlobalFunctionCollection;
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver;
use Humbug\PhpScoper\Whitelist;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeVisitorAbstract;
use function count;

/**
* @TODO
*
* @private
*/
final class FunctionIdentifierRecorder extends NodeVisitorAbstract
{
private $prefix;
private $nameResolver;
private $whitelist;

public function __construct(
string $prefix,
FullyQualifiedNameResolver $nameResolver,
Whitelist $whitelist
) {
$this->prefix = $prefix;
$this->nameResolver = $nameResolver;
$this->whitelist = $whitelist;
}

/**
* @inheritdoc
*/
public function enterNode(Node $node): Node
{
if (false === ($node instanceof Identifier) || false === AppendParentNode::hasParent($node)) {
return $node;
}

$parent = AppendParentNode::getParent($node);

if (false === ($parent instanceof Function_)) {
return $node;
}

/** @var Identifier $node */

$resolvedValue = $this->nameResolver->resolveName(
new Name(
$node->name,
$node->getAttributes()
)
);
$resolvedName = $resolvedValue->getName();

if (null !== $resolvedValue->getNamespace()
|| false === ($resolvedName instanceof FullyQualified)
|| count($resolvedName->parts) > 1
) {
return $node;
}

/** @var FullyQualified $resolvedName */

$this->whitelist->recordUserGlobalFunction(
$resolvedName,
FullyQualified::concat($this->prefix, $resolvedName)
);

return $node;
}
}
2 changes: 1 addition & 1 deletion src/PhpParser/NodeVisitor/StringScalarPrefixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function enterNode(Node $node): Node
private function shouldPrefixScalar(Node $node, bool &$isSpecialFunction): bool
{
if (false === ($node instanceof String_ && AppendParentNode::hasParent($node) && is_string($node->value))
|| 1 !== preg_match('/^((\\\\)?[\p{L}_]+)|((\\\\)?(?:[\p{L}_]+\\\\+)+[\p{L}_]+)$/u', $node->value)
|| 1 !== preg_match('/^((\\\\)?[\p{L}_]+)$|((\\\\)?(?:[\p{L}_]+\\\\+)+[\p{L}_]+)$/u', $node->value)
) {
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/PhpParser/TraverserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace Humbug\PhpScoper\PhpParser;

use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UserGlobalFunctionCollection;
use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\NamespaceStmtCollection;
use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UseStmtCollection;
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver;
Expand Down Expand Up @@ -49,6 +50,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter
$traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements));
$traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $this->reflector));

$traverser->addVisitor(new NodeVisitor\FunctionIdentifierRecorder($prefix, $nameResolver, $whitelist));
$traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector));
$traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector));

Expand Down
24 changes: 24 additions & 0 deletions src/Whitelist.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace Humbug\PhpScoper;

use Countable;
use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UserGlobalFunctionCollection;
use InvalidArgumentException;
use function array_filter;
use function array_map;
Expand All @@ -24,6 +25,7 @@
use function explode;
use function implode;
use function in_array;
use PhpParser\Node\Name\FullyQualified;
use function sprintf;
use function strtolower;
use function substr;
Expand All @@ -36,6 +38,8 @@ final class Whitelist implements Countable
private $constants;
private $namespaces;
private $whitelistGlobalConstants;
private $whitelistGlobalFunctions;
private $userGlobalFunctions;

public static function create(bool $whitelistGlobalConstants, bool $whitelistGlobalFunctions,string ...$elements): self
{
Expand Down Expand Up @@ -72,6 +76,7 @@ public static function create(bool $whitelistGlobalConstants, bool $whitelistGlo

return new self(
$whitelistGlobalConstants,
$whitelistGlobalFunctions,
array_unique($original),
array_unique($classes),
array_unique($constants),
Expand All @@ -87,23 +92,42 @@ public static function create(bool $whitelistGlobalConstants, bool $whitelistGlo
*/
private function __construct(
bool $whitelistGlobalConstants,
bool $whitelistGlobalFunctions,
array $original,
array $classes,
array $constants,
array $namespaces
) {
$this->whitelistGlobalConstants = $whitelistGlobalConstants;
$this->whitelistGlobalFunctions = $whitelistGlobalFunctions;
$this->original = $original;
$this->classes = $classes;
$this->constants = $constants;
$this->namespaces = $namespaces;
$this->userGlobalFunctions = new UserGlobalFunctionCollection();
}

public function recordUserGlobalFunction(FullyQualified $original, FullyQualified $alias): void
{
$this->userGlobalFunctions->add($original, $alias);
}

public function getUserGlobalFunctions(): UserGlobalFunctionCollection
{
return $this->userGlobalFunctions;
}

public function whitelistGlobalConstants(): bool
{
return $this->whitelistGlobalConstants;
}

public function whitelistGlobalFunctions(): bool
{
// TODO: check that nothing is appended/collected if everything is being whitelisted; avoid the collection in this case to avoid performance issues
return $this->whitelistGlobalFunctions;
}

public function isClassWhitelisted(string $name): bool
{
return in_array(strtolower($name), $this->classes, true);
Expand Down
Loading

0 comments on commit 8a8736a

Please sign in to comment.