Skip to content

Commit f1e494a

Browse files
authored
fix: Correctly scope is_callable() calls (#888)
Closes #299.
1 parent 3dde183 commit f1e494a

File tree

2 files changed

+263
-1
lines changed

2 files changed

+263
-1
lines changed
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the humbug/php-scoper package.
7+
*
8+
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
9+
* Pádraic Brady <[email protected]>
10+
*
11+
* For the full copyright and license information, please view the LICENSE
12+
* file that was distributed with this source code.
13+
*/
14+
15+
return [
16+
'meta' => [
17+
'title' => 'String literal used as a function argument of an is_callable call',
18+
// Default values. If not specified will be the one used
19+
'prefix' => 'Humbug',
20+
21+
'expose-global-constants' => false,
22+
'expose-global-classes' => false,
23+
'expose-global-functions' => false,
24+
'expose-namespaces' => [],
25+
'expose-constants' => [],
26+
'expose-classes' => [],
27+
'expose-functions' => [],
28+
29+
'exclude-namespaces' => [],
30+
'exclude-constants' => [],
31+
'exclude-classes' => [],
32+
'exclude-functions' => [],
33+
34+
'expected-recorded-classes' => [],
35+
'expected-recorded-functions' => [],
36+
],
37+
38+
'FQCN string argument' => <<<'PHP'
39+
<?php
40+
41+
is_callable('sodiumCompatAutoloader');
42+
is_callable('Sodium\compatAutoloader');
43+
is_callable(['Swift', 'autoload']);
44+
is_callable(['\Swift', 'autoload']);
45+
is_callable(['Humbug\\Swift', 'autoload']);
46+
is_callable(['\\Humbug\\Swift', 'autoload']);
47+
is_callable(['\\Humbug\\Swift', 'autoload']);
48+
is_callable(['DateTime', 'autoload']);
49+
is_callable(['\\DateTime', 'autoload']);
50+
51+
----
52+
<?php
53+
54+
namespace Humbug;
55+
56+
\is_callable('Humbug\\sodiumCompatAutoloader');
57+
\is_callable('Humbug\\Sodium\\compatAutoloader');
58+
\is_callable(['Humbug\\Swift', 'autoload']);
59+
\is_callable(['Humbug\\Swift', 'autoload']);
60+
\is_callable(['Humbug\\Swift', 'autoload']);
61+
\is_callable(['Humbug\\Swift', 'autoload']);
62+
\is_callable(['Humbug\\Swift', 'autoload']);
63+
\is_callable(['DateTime', 'autoload']);
64+
\is_callable(['\\DateTime', 'autoload']);
65+
66+
PHP,
67+
68+
'FQCN string argument on exposed class' => [
69+
'expose-classes' => ['Symfony\Component\Yaml\Yaml', 'Swift'],
70+
'payload' => <<<'PHP'
71+
<?php
72+
73+
is_callable(['Swift', 'autoload']);
74+
is_callable(['Humbug\\Swift', 'autoload']);
75+
is_callable(['\\Humbug\\Swift', 'autoload']);
76+
is_callable(['DateTime', 'autoload']);
77+
78+
----
79+
<?php
80+
81+
namespace Humbug;
82+
83+
\is_callable(['Humbug\\Swift', 'autoload']);
84+
\is_callable(['Humbug\\Swift', 'autoload']);
85+
\is_callable(['Humbug\\Swift', 'autoload']);
86+
\is_callable(['DateTime', 'autoload']);
87+
88+
PHP,
89+
],
90+
91+
'FQCN string argument on exposed function' => [
92+
'expose-functions' => ['sodiumCompatAutoloader'],
93+
'payload' => <<<'PHP'
94+
<?php
95+
96+
is_callable('sodiumCompatAutoloader');
97+
98+
----
99+
<?php
100+
101+
namespace Humbug;
102+
103+
\is_callable('Humbug\\sodiumCompatAutoloader');
104+
105+
PHP,
106+
],
107+
108+
'FQCN string argument on class from an excluded namespace' => [
109+
'exclude-namespaces' => [
110+
'Symfony\Component\Yaml',
111+
'/^$/',
112+
],
113+
'payload' => <<<'PHP'
114+
<?php
115+
116+
is_callable(['Swift', 'autoload']);
117+
is_callable(['Humbug\\Swift', 'autoload']);
118+
is_callable(['\\Humbug\\Swift', 'autoload']);
119+
is_callable(['DateTime', 'autoload']);
120+
121+
----
122+
<?php
123+
124+
namespace {
125+
\is_callable(['Swift', 'autoload']);
126+
\is_callable(['Humbug\\Swift', 'autoload']);
127+
\is_callable(['Humbug\\Swift', 'autoload']);
128+
\is_callable(['DateTime', 'autoload']);
129+
}
130+
131+
PHP,
132+
],
133+
134+
'FQCN string argument on function from an excluded namespace' => [
135+
'exclude-namespaces' => [
136+
'Sodium',
137+
'/^$/',
138+
],
139+
'payload' => <<<'PHP'
140+
<?php
141+
142+
is_callable('Sodium\CompatAutoloader');
143+
144+
----
145+
<?php
146+
147+
namespace {
148+
\is_callable('Sodium\\CompatAutoloader');
149+
}
150+
151+
PHP,
152+
],
153+
154+
'FQCN string argument with global functions not exposed' => [
155+
'expose-global-functions' => false,
156+
'payload' => <<<'PHP'
157+
<?php
158+
159+
is_callable(['Swift', 'autoload']);
160+
is_callable(['Humbug\\Swift', 'autoload']);
161+
is_callable(['\\Humbug\\Swift', 'autoload']);
162+
is_callable(['DateTime', 'autoload']);
163+
164+
----
165+
<?php
166+
167+
namespace Humbug;
168+
169+
\is_callable(['Humbug\\Swift', 'autoload']);
170+
\is_callable(['Humbug\\Swift', 'autoload']);
171+
\is_callable(['Humbug\\Swift', 'autoload']);
172+
\is_callable(['DateTime', 'autoload']);
173+
174+
PHP,
175+
],
176+
177+
'FQCN string argument formed by concatenated strings' => <<<'PHP'
178+
<?php
179+
180+
is_callable(['Swift'.'', 'autoload']);
181+
182+
----
183+
<?php
184+
185+
namespace Humbug;
186+
187+
\is_callable(['Swift' . '', 'autoload']);
188+
189+
PHP,
190+
191+
'FQC constant call' => <<<'PHP'
192+
<?php
193+
194+
namespace Symfony\Component\Yaml {
195+
class Yaml {}
196+
}
197+
198+
namespace {
199+
is_callable([\Swift::class, 'autoload']);
200+
is_callable([\Humbug\Swift::class, 'autoload']);
201+
is_callable([\DateTime::class, 'autoload']);
202+
}
203+
----
204+
<?php
205+
206+
namespace Humbug\Symfony\Component\Yaml;
207+
208+
class Yaml
209+
{
210+
}
211+
namespace Humbug;
212+
213+
\is_callable([\Humbug\Swift::class, 'autoload']);
214+
\is_callable([\Humbug\Swift::class, 'autoload']);
215+
\is_callable([\DateTime::class, 'autoload']);
216+
217+
PHP,
218+
219+
'FQC constant call on exposed class' => [
220+
'expose-classes' => ['Symfony\Component\Yaml\Ya_1'],
221+
'expected-recorded-classes' => [
222+
['Symfony\Component\Yaml\Ya_1', 'Humbug\Symfony\Component\Yaml\Ya_1'],
223+
],
224+
'payload' => <<<'PHP'
225+
<?php
226+
227+
namespace Symfony\Component\Yaml {
228+
class Ya_1 {}
229+
}
230+
231+
namespace {
232+
is_callable([Symfony\Component\Yaml\Ya_1::class, 'autoload']);
233+
is_callable([\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
234+
is_callable([Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
235+
is_callable([\Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
236+
}
237+
----
238+
<?php
239+
240+
namespace Humbug\Symfony\Component\Yaml;
241+
242+
class Ya_1
243+
{
244+
}
245+
\class_alias('Humbug\\Symfony\\Component\\Yaml\\Ya_1', 'Symfony\\Component\\Yaml\\Ya_1', \false);
246+
namespace Humbug;
247+
248+
\is_callable([\Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
249+
\is_callable([\Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
250+
\is_callable([\Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
251+
\is_callable([\Humbug\Symfony\Component\Yaml\Ya_1::class, 'autoload']);
252+
253+
PHP
254+
],
255+
];

src/PhpParser/NodeVisitor/StringScalarPrefixer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,18 @@ final class StringScalarPrefixer extends NodeVisitorAbstract
7979
'function_exists',
8080
'interface_exists',
8181
'is_a',
82+
'is_callable',
8283
'is_subclass_of',
8384
'spl_autoload_register',
8485
'trait_exists',
8586
];
8687

88+
// Function for which we know the FIRST argument IS a FQCN
89+
private const SPECIAL_ARRAY_FUNCTION_NAMES = [
90+
'is_callable',
91+
'spl_autoload_register',
92+
];
93+
8794
private const DATETIME_CLASSES = [
8895
'datetime',
8996
'datetimeimmutable',
@@ -315,7 +322,7 @@ private function prefixArrayItemString(
315322

316323
$functionName = (string) $functionNode->name;
317324

318-
return ('spl_autoload_register' === $functionName
325+
return (in_array($functionName, self::SPECIAL_ARRAY_FUNCTION_NAMES, true)
319326
&& array_key_exists(0, $arrayNode->items)
320327
&& $arrayItemNode === $arrayNode->items[0]
321328
&& !$this->enrichedReflector->isClassExcluded($normalizedValue)

0 commit comments

Comments
 (0)