Skip to content

Commit 084f6ab

Browse files
committed
Implement GH-10024: support linting multiple files at once using php -l
This is supported in both the CLI and CGI modes. For CLI this required little changes. For CGI, the tricky part was that the options parsing happens inside the loop. This means that options passed after the -l flag were previously simply ignored. As we now re-enter the loop we would parse the options again, and if they are handled but don't set the script name, then CGI will think you want to read from standard in. To keep the same "don't parse options" behaviour I simply wrapped the options handling inside an if.
1 parent f378842 commit 084f6ab

File tree

4 files changed

+340
-100
lines changed

4 files changed

+340
-100
lines changed

sapi/cgi/cgi_main.c

Lines changed: 105 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,107 +2268,109 @@ consult the installation file that came with this distribution, or visit \n\
22682268
init_request_info(request);
22692269

22702270
if (!cgi && !fastcgi) {
2271-
while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
2272-
switch (c) {
2273-
2274-
case 'a': /* interactive mode */
2275-
printf("Interactive mode enabled\n\n");
2276-
fflush(stdout);
2277-
break;
2278-
2279-
case 'C': /* don't chdir to the script directory */
2280-
SG(options) |= SAPI_OPTION_NO_CHDIR;
2281-
break;
2282-
2283-
case 'e': /* enable extended info output */
2284-
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
2285-
break;
2286-
2287-
case 'f': /* parse file */
2288-
if (script_file) {
2289-
efree(script_file);
2290-
}
2291-
script_file = estrdup(php_optarg);
2292-
no_headers = 1;
2293-
break;
2271+
if (behavior != PHP_MODE_LINT) {
2272+
while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
2273+
switch (c) {
2274+
2275+
case 'a': /* interactive mode */
2276+
printf("Interactive mode enabled\n\n");
2277+
fflush(stdout);
2278+
break;
2279+
2280+
case 'C': /* don't chdir to the script directory */
2281+
SG(options) |= SAPI_OPTION_NO_CHDIR;
2282+
break;
2283+
2284+
case 'e': /* enable extended info output */
2285+
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
2286+
break;
2287+
2288+
case 'f': /* parse file */
2289+
if (script_file) {
2290+
efree(script_file);
2291+
}
2292+
script_file = estrdup(php_optarg);
2293+
no_headers = 1;
2294+
break;
22942295

2295-
case 'i': /* php info & quit */
2296-
if (script_file) {
2297-
efree(script_file);
2298-
}
2299-
if (php_request_startup() == FAILURE) {
2300-
SG(server_context) = NULL;
2301-
php_module_shutdown();
2302-
free(bindpath);
2303-
return FAILURE;
2304-
}
2305-
if (no_headers) {
2296+
case 'i': /* php info & quit */
2297+
if (script_file) {
2298+
efree(script_file);
2299+
}
2300+
if (php_request_startup() == FAILURE) {
2301+
SG(server_context) = NULL;
2302+
php_module_shutdown();
2303+
free(bindpath);
2304+
return FAILURE;
2305+
}
2306+
if (no_headers) {
2307+
SG(headers_sent) = 1;
2308+
SG(request_info).no_headers = 1;
2309+
}
2310+
php_print_info(0xFFFFFFFF);
2311+
php_request_shutdown((void *) 0);
2312+
fcgi_shutdown();
2313+
exit_status = 0;
2314+
goto out;
2315+
2316+
case 'l': /* syntax check mode */
2317+
no_headers = 1;
2318+
behavior = PHP_MODE_LINT;
2319+
break;
2320+
2321+
case 'm': /* list compiled in modules */
2322+
if (script_file) {
2323+
efree(script_file);
2324+
}
2325+
SG(headers_sent) = 1;
2326+
php_printf("[PHP Modules]\n");
2327+
print_modules();
2328+
php_printf("\n[Zend Modules]\n");
2329+
print_extensions();
2330+
php_printf("\n");
2331+
php_output_end_all();
2332+
fcgi_shutdown();
2333+
exit_status = 0;
2334+
goto out;
2335+
2336+
case 'q': /* do not generate HTTP headers */
2337+
no_headers = 1;
2338+
break;
2339+
2340+
case 'v': /* show php version & quit */
2341+
if (script_file) {
2342+
efree(script_file);
2343+
}
2344+
no_headers = 1;
2345+
if (php_request_startup() == FAILURE) {
2346+
SG(server_context) = NULL;
2347+
php_module_shutdown();
2348+
free(bindpath);
2349+
return FAILURE;
2350+
}
23062351
SG(headers_sent) = 1;
23072352
SG(request_info).no_headers = 1;
2308-
}
2309-
php_print_info(0xFFFFFFFF);
2310-
php_request_shutdown((void *) 0);
2311-
fcgi_shutdown();
2312-
exit_status = 0;
2313-
goto out;
2314-
2315-
case 'l': /* syntax check mode */
2316-
no_headers = 1;
2317-
behavior = PHP_MODE_LINT;
2318-
break;
2319-
2320-
case 'm': /* list compiled in modules */
2321-
if (script_file) {
2322-
efree(script_file);
2323-
}
2324-
SG(headers_sent) = 1;
2325-
php_printf("[PHP Modules]\n");
2326-
print_modules();
2327-
php_printf("\n[Zend Modules]\n");
2328-
print_extensions();
2329-
php_printf("\n");
2330-
php_output_end_all();
2331-
fcgi_shutdown();
2332-
exit_status = 0;
2333-
goto out;
2334-
2335-
case 'q': /* do not generate HTTP headers */
2336-
no_headers = 1;
2337-
break;
2338-
2339-
case 'v': /* show php version & quit */
2340-
if (script_file) {
2341-
efree(script_file);
2342-
}
2343-
no_headers = 1;
2344-
if (php_request_startup() == FAILURE) {
2345-
SG(server_context) = NULL;
2346-
php_module_shutdown();
2347-
free(bindpath);
2348-
return FAILURE;
2349-
}
2350-
SG(headers_sent) = 1;
2351-
SG(request_info).no_headers = 1;
2352-
#if ZEND_DEBUG
2353-
php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2354-
#else
2355-
php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2356-
#endif
2357-
php_request_shutdown((void *) 0);
2358-
fcgi_shutdown();
2359-
exit_status = 0;
2360-
goto out;
2361-
2362-
case 'w':
2363-
behavior = PHP_MODE_STRIP;
2364-
break;
2365-
2366-
case 'z': /* load extension file */
2367-
zend_load_extension(php_optarg);
2368-
break;
2369-
2370-
default:
2371-
break;
2353+
#if ZEND_DEBUG
2354+
php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2355+
#else
2356+
php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2357+
#endif
2358+
php_request_shutdown((void *) 0);
2359+
fcgi_shutdown();
2360+
exit_status = 0;
2361+
goto out;
2362+
2363+
case 'w':
2364+
behavior = PHP_MODE_STRIP;
2365+
break;
2366+
2367+
case 'z': /* load extension file */
2368+
zend_load_extension(php_optarg);
2369+
break;
2370+
2371+
default:
2372+
break;
2373+
}
23722374
}
23732375
}
23742376

@@ -2512,7 +2514,6 @@ consult the installation file that came with this distribution, or visit \n\
25122514
PG(during_request_startup) = 0;
25132515
if (php_lint_script(&file_handle) == SUCCESS) {
25142516
zend_printf("No syntax errors detected in %s\n", ZSTR_VAL(file_handle.filename));
2515-
exit_status = 0;
25162517
} else {
25172518
zend_printf("Errors parsing %s\n", ZSTR_VAL(file_handle.filename));
25182519
exit_status = -1;
@@ -2581,6 +2582,11 @@ consult the installation file that came with this distribution, or visit \n\
25812582
}
25822583
}
25832584
}
2585+
if (behavior == PHP_MODE_LINT && argc - 1 > php_optind) {
2586+
php_optind++;
2587+
script_file = NULL;
2588+
continue;
2589+
}
25842590
break;
25852591
}
25862592

sapi/cgi/tests/012.phpt

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
--TEST--
2+
multiple files syntax check
3+
--SKIPIF--
4+
<?php include "skipif.inc"; ?>
5+
--INI--
6+
display_errors=stdout
7+
--FILE--
8+
<?php
9+
include "include.inc";
10+
11+
function run_and_output($cmd) {
12+
if (!defined("PHP_WINDOWS_VERSION_MAJOR")) {
13+
$cmd .= " 2>/dev/null";
14+
}
15+
exec($cmd, $output, $exit_code);
16+
print_r($output);
17+
var_dump($exit_code == 0);
18+
}
19+
20+
$php = get_cgi_path();
21+
reset_env_vars();
22+
23+
$filename_good = __DIR__."/012_good.test.php";
24+
$filename_good_escaped = escapeshellarg($filename_good);
25+
$filename_bad = __DIR__."/012_bad.test.php";
26+
$filename_bad_escaped = escapeshellarg($filename_bad);
27+
28+
$code = '
29+
<?php
30+
31+
echo "hi";
32+
';
33+
34+
file_put_contents($filename_good, $code);
35+
36+
run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped");
37+
run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped");
38+
39+
$code = '
40+
<?php
41+
42+
class test
43+
private $var;
44+
}
45+
46+
?>
47+
';
48+
49+
file_put_contents($filename_bad, $code);
50+
51+
run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped");
52+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped");
53+
run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped");
54+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown");
55+
56+
echo "Done\n";
57+
?>
58+
--CLEAN--
59+
<?php
60+
@unlink($filename_good);
61+
@unlink($filename_bad);
62+
?>
63+
--EXPECTF--
64+
Array
65+
(
66+
[0] => No syntax errors detected in %s012_good.test.php
67+
[1] => No syntax errors detected in %s012_good.test.php
68+
)
69+
bool(true)
70+
Array
71+
(
72+
[0] => No syntax errors detected in %s012_good.test.php
73+
[1] => No input file specified.
74+
)
75+
bool(false)
76+
Array
77+
(
78+
[0] => No syntax errors detected in %s012_good.test.php
79+
[1] => <br />
80+
[2] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
81+
[3] => Errors parsing %s012_bad.test.php
82+
[4] => No syntax errors detected in %s012_good.test.php
83+
)
84+
bool(false)
85+
Array
86+
(
87+
[0] => <br />
88+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
89+
[2] => Errors parsing %s012_bad.test.php
90+
[3] => <br />
91+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
92+
[5] => Errors parsing %s012_bad.test.php
93+
)
94+
bool(false)
95+
Array
96+
(
97+
[0] => <br />
98+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
99+
[2] => Errors parsing %s012_bad.test.php
100+
[3] => No input file specified.
101+
)
102+
bool(false)
103+
Array
104+
(
105+
[0] => <br />
106+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
107+
[2] => Errors parsing %s012_bad.test.php
108+
[3] => <br />
109+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
110+
[5] => Errors parsing %s012_bad.test.php
111+
[6] => No input file specified.
112+
)
113+
bool(false)
114+
Done

sapi/cli/php_cli.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,10 @@ static int do_cli(int argc, char **argv) /* {{{ */
734734
break;
735735
}
736736
behavior=PHP_MODE_LINT;
737+
/* We want to set the error exit status if at least one lint failed.
738+
* If all were successful we set the exit status to 0.
739+
* We already set EG(exit_status) here such that only failures set the exit status. */
740+
EG(exit_status) = 0;
737741
break;
738742

739743
case 'q': /* do not generate HTTP headers */
@@ -962,7 +966,6 @@ static int do_cli(int argc, char **argv) /* {{{ */
962966
case PHP_MODE_LINT:
963967
if (php_lint_script(&file_handle) == SUCCESS) {
964968
zend_printf("No syntax errors detected in %s\n", php_self);
965-
EG(exit_status) = 0;
966969
} else {
967970
zend_printf("Errors parsing %s\n", php_self);
968971
EG(exit_status) = 255;
@@ -1132,6 +1135,12 @@ static int do_cli(int argc, char **argv) /* {{{ */
11321135
if (translated_path) {
11331136
free(translated_path);
11341137
}
1138+
if (behavior == PHP_MODE_LINT && argc > php_optind && strcmp(argv[php_optind],"--")) {
1139+
script_file = NULL;
1140+
request_started = 0;
1141+
translated_path = NULL;
1142+
goto do_repeat;
1143+
}
11351144
/* Don't repeat fork()ed processes. */
11361145
if (--num_repeats && pid == getpid()) {
11371146
fprintf(stdout, "Finished execution, repeating...\n");

0 commit comments

Comments
 (0)