Issues (731)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

third party/jsminplus.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * JSMinPlus version 1.4
5
 *
6
 * Minifies a javascript file using a javascript parser
7
 *
8
 * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
9
 * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
10
 * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
11
 * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
12
 *
13
 * Tino Zijdel <[email protected]>
14
 *
15
 * Usage: $minified = JSMinPlus::minify($script [, $filename])
16
 *
17
 * Versionlog (see also changelog.txt):
18
 * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
19
 *              reduce memory footprint by minifying by block-scope
20
 *              some small byte-saving and performance improvements
21
 * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
22
 * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
23
 * 12-04-2009 - some small bugfixes and performance improvements
24
 * 09-04-2009 - initial open sourced version 1.0
25
 *
26
 * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
27
 *
28
 */
29
30
/* ***** BEGIN LICENSE BLOCK *****
31
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
32
 *
33
 * The contents of this file are subject to the Mozilla Public License Version
34
 * 1.1 (the "License"); you may not use this file except in compliance with
35
 * the License. You may obtain a copy of the License at
36
 * http://www.mozilla.org/MPL/
37
 *
38
 * Software distributed under the License is distributed on an "AS IS" basis,
39
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
40
 * for the specific language governing rights and limitations under the
41
 * License.
42
 *
43
 * The Original Code is the Narcissus JavaScript engine.
44
 *
45
 * The Initial Developer of the Original Code is
46
 * Brendan Eich <[email protected]>.
47
 * Portions created by the Initial Developer are Copyright (C) 2004
48
 * the Initial Developer. All Rights Reserved.
49
 *
50
 * Contributor(s): Tino Zijdel <[email protected]>
51
 * PHP port, modifications and minifier routine are (C) 2009-2011
52
 *
53
 * Alternatively, the contents of this file may be used under the terms of
54
 * either the GNU General Public License Version 2 or later (the "GPL"), or
55
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
56
 * in which case the provisions of the GPL or the LGPL are applicable instead
57
 * of those above. If you wish to allow use of your version of this file only
58
 * under the terms of either the GPL or the LGPL, and not to allow others to
59
 * use your version of this file under the terms of the MPL, indicate your
60
 * decision by deleting the provisions above and replace them with the notice
61
 * and other provisions required by the GPL or the LGPL. If you do not delete
62
 * the provisions above, a recipient may use your version of this file under
63
 * the terms of any one of the MPL, the GPL or the LGPL.
64
 *
65
 * ***** END LICENSE BLOCK ***** */
66
67
define('TOKEN_END', 1);
68
define('TOKEN_NUMBER', 2);
69
define('TOKEN_IDENTIFIER', 3);
70
define('TOKEN_STRING', 4);
71
define('TOKEN_REGEXP', 5);
72
define('TOKEN_NEWLINE', 6);
73
define('TOKEN_CONDCOMMENT_START', 7);
74
define('TOKEN_CONDCOMMENT_END', 8);
75
76
define('JS_SCRIPT', 100);
77
define('JS_BLOCK', 101);
78
define('JS_LABEL', 102);
79
define('JS_FOR_IN', 103);
80
define('JS_CALL', 104);
81
define('JS_NEW_WITH_ARGS', 105);
82
define('JS_INDEX', 106);
83
define('JS_ARRAY_INIT', 107);
84
define('JS_OBJECT_INIT', 108);
85
define('JS_PROPERTY_INIT', 109);
86
define('JS_GETTER', 110);
87
define('JS_SETTER', 111);
88
define('JS_GROUP', 112);
89
define('JS_LIST', 113);
90
91
define('JS_MINIFIED', 999);
92
93
define('DECLARED_FORM', 0);
94
define('EXPRESSED_FORM', 1);
95
define('STATEMENT_FORM', 2);
96
97
/* Operators */
98
define('OP_SEMICOLON', ';');
99
define('OP_COMMA', ',');
100
define('OP_HOOK', '?');
101
define('OP_COLON', ':');
102
define('OP_OR', '||');
103
define('OP_AND', '&&');
104
define('OP_BITWISE_OR', '|');
105
define('OP_BITWISE_XOR', '^');
106
define('OP_BITWISE_AND', '&');
107
define('OP_STRICT_EQ', '===');
108
define('OP_EQ', '==');
109
define('OP_ASSIGN', '=');
110
define('OP_STRICT_NE', '!==');
111
define('OP_NE', '!=');
112
define('OP_LSH', '<<');
113
define('OP_LE', '<=');
114
define('OP_LT', '<');
115
define('OP_URSH', '>>>');
116
define('OP_RSH', '>>');
117
define('OP_GE', '>=');
118
define('OP_GT', '>');
119
define('OP_INCREMENT', '++');
120
define('OP_DECREMENT', '--');
121
define('OP_PLUS', '+');
122
define('OP_MINUS', '-');
123
define('OP_MUL', '*');
124
define('OP_DIV', '/');
125
define('OP_MOD', '%');
126
define('OP_NOT', '!');
127
define('OP_BITWISE_NOT', '~');
128
define('OP_DOT', '.');
129
define('OP_LEFT_BRACKET', '[');
130
define('OP_RIGHT_BRACKET', ']');
131
define('OP_LEFT_CURLY', '{');
132
define('OP_RIGHT_CURLY', '}');
133
define('OP_LEFT_PAREN', '(');
134
define('OP_RIGHT_PAREN', ')');
135
define('OP_CONDCOMMENT_END', '@*/');
136
137
define('OP_UNARY_PLUS', 'U+');
138
define('OP_UNARY_MINUS', 'U-');
139
140
/* Keywords */
141
define('KEYWORD_BREAK', 'break');
142
define('KEYWORD_CASE', 'case');
143
define('KEYWORD_CATCH', 'catch');
144
define('KEYWORD_CONST', 'const');
145
define('KEYWORD_CONTINUE', 'continue');
146
define('KEYWORD_DEBUGGER', 'debugger');
147
define('KEYWORD_DEFAULT', 'default');
148
define('KEYWORD_DELETE', 'delete');
149
define('KEYWORD_DO', 'do');
150
define('KEYWORD_ELSE', 'else');
151
define('KEYWORD_ENUM', 'enum');
152
define('KEYWORD_FALSE', 'false');
153
define('KEYWORD_FINALLY', 'finally');
154
define('KEYWORD_FOR', 'for');
155
define('KEYWORD_FUNCTION', 'function');
156
define('KEYWORD_IF', 'if');
157
define('KEYWORD_IN', 'in');
158
define('KEYWORD_INSTANCEOF', 'instanceof');
159
define('KEYWORD_NEW', 'new');
160
define('KEYWORD_NULL', 'null');
161
define('KEYWORD_RETURN', 'return');
162
define('KEYWORD_SWITCH', 'switch');
163
define('KEYWORD_THIS', 'this');
164
define('KEYWORD_THROW', 'throw');
165
define('KEYWORD_TRUE', 'true');
166
define('KEYWORD_TRY', 'try');
167
define('KEYWORD_TYPEOF', 'typeof');
168
define('KEYWORD_VAR', 'var');
169
define('KEYWORD_VOID', 'void');
170
define('KEYWORD_WHILE', 'while');
171
define('KEYWORD_WITH', 'with');
172
173
174
class JSMinPlus
175
{
176
	private $parser;
177
	private $reserved = array(
178
		'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
179
		'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
180
		'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
181
		'void', 'while', 'with',
182
		// Words reserved for future use
183
		'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
184
		'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
185
		'implements', 'import', 'int', 'interface', 'long', 'native',
186
		'package', 'private', 'protected', 'public', 'short', 'static',
187
		'super', 'synchronized', 'throws', 'transient', 'volatile',
188
		// These are not reserved, but should be taken into account
189
		// in isValidIdentifier (See jslint source code)
190
		'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
191
	);
192
193
	private function __construct()
194
	{
195
		$this->parser = new JSParser($this);
196
	}
197
198
	public static function minify($js, $filename='')
199
	{
200
		static $instance;
201
202
		// this is a singleton
203
		if(!$instance)
204
			$instance = new JSMinPlus();
205
206
		return $instance->min($js, $filename);
207
	}
208
209
	private function min($js, $filename)
210
	{
211
		try
212
		{
213
			$n = $this->parser->parse($js, $filename, 1);
214
			return $this->parseTree($n);
215
		}
216
		catch(Exception $e)
217
		{
218
			echo $e->getMessage() . "\n";
219
		}
220
221
		return false;
222
	}
223
224
	public function parseTree($n, $noBlockGrouping = false)
225
	{
226
		$s = '';
227
228
		switch ($n->type)
229
		{
230
			case JS_MINIFIED:
231
				$s = $n->value;
232
			break;
233
234
			case JS_SCRIPT:
235
				// we do nothing yet with funDecls or varDecls
236
				$noBlockGrouping = true;
237
			// FALL THROUGH
238
239
			case JS_BLOCK:
240
				$childs = $n->treeNodes;
241
				$lastType = 0;
242
				for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
243
				{
244
					$type = $childs[$i]->type;
245
					$t = $this->parseTree($childs[$i]);
246
					if (strlen($t))
247
					{
248
						if ($c)
249
						{
250
							$s = rtrim($s, ';');
251
252
							if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
253
							{
254
								// put declared functions on a new line
255
								$s .= "\n";
256
							}
257
							elseif ($type == KEYWORD_VAR && $type == $lastType)
258
							{
259
								// mutiple var-statements can go into one
260
								$t = ',' . substr($t, 4);
261
							}
262
							else
263
							{
264
								// add terminator
265
								$s .= ';';
266
							}
267
						}
268
269
						$s .= $t;
270
271
						$c++;
272
						$lastType = $type;
273
					}
274
				}
275
276
				if ($c > 1 && !$noBlockGrouping)
277
				{
278
					$s = '{' . $s . '}';
279
				}
280
			break;
281
282
			case KEYWORD_FUNCTION:
283
				$s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
284
				$params = $n->params;
285
				for ($i = 0, $j = count($params); $i < $j; $i++)
286
					$s .= ($i ? ',' : '') . $params[$i];
287
				$s .= '){' . $this->parseTree($n->body, true) . '}';
288
			break;
289
290
			case KEYWORD_IF:
291
				$s = 'if(' . $this->parseTree($n->condition) . ')';
292
				$thenPart = $this->parseTree($n->thenPart);
293
				$elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
294
295
				// empty if-statement
296
				if ($thenPart == '')
297
					$thenPart = ';';
298
299
				if ($elsePart)
300
				{
301
					// be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
302
					if ($thenPart != ';' && $thenPart[0] != '{')
303
						$thenPart = '{' . $thenPart . '}';
304
305
					$s .= $thenPart . 'else';
306
307
					// we could check for more, but that hardly ever applies so go for performance
308
					if ($elsePart[0] != '{')
309
						$s .= ' ';
310
311
					$s .= $elsePart;
312
				}
313
				else
314
				{
315
					$s .= $thenPart;
316
				}
317
			break;
318
319
			case KEYWORD_SWITCH:
320
				$s = 'switch(' . $this->parseTree($n->discriminant) . '){';
321
				$cases = $n->cases;
322
				for ($i = 0, $j = count($cases); $i < $j; $i++)
323
				{
324
					$case = $cases[$i];
325
					if ($case->type == KEYWORD_CASE)
326
						$s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
327
					else
328
						$s .= 'default:';
329
330
					$statement = $this->parseTree($case->statements, true);
331
					if ($statement)
332
					{
333
						$s .= $statement;
334
						// no terminator for last statement
335
						if ($i + 1 < $j)
336
							$s .= ';';
337
					}
338
				}
339
				$s .= '}';
340
			break;
341
342
			case KEYWORD_FOR:
343
				$s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
344
					. ';' . ($n->condition ? $this->parseTree($n->condition) : '')
345
					. ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
346
347
				$body  = $this->parseTree($n->body);
348
				if ($body == '')
349
					$body = ';';
350
351
				$s .= $body;
352
			break;
353
354
			case KEYWORD_WHILE:
355
				$s = 'while(' . $this->parseTree($n->condition) . ')';
356
357
				$body  = $this->parseTree($n->body);
358
				if ($body == '')
359
					$body = ';';
360
361
				$s .= $body;
362
			break;
363
364
			case JS_FOR_IN:
365
				$s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
366
367
				$body  = $this->parseTree($n->body);
368
				if ($body == '')
369
					$body = ';';
370
371
				$s .= $body;
372
			break;
373
374
			case KEYWORD_DO:
375
				$s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
376
			break;
377
378
			case KEYWORD_BREAK:
379
			case KEYWORD_CONTINUE:
380
				$s = $n->value . ($n->label ? ' ' . $n->label : '');
381
			break;
382
383
			case KEYWORD_TRY:
384
				$s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
385
				$catchClauses = $n->catchClauses;
386
				for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
387
				{
388
					$t = $catchClauses[$i];
389
					$s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
390
				}
391
				if ($n->finallyBlock)
392
					$s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
393
			break;
394
395
			case KEYWORD_THROW:
396
			case KEYWORD_RETURN:
397
				$s = $n->type;
398
				if ($n->value)
399
				{
400
					$t = $this->parseTree($n->value);
401
					if (strlen($t))
402
					{
403
						if ($this->isWordChar($t[0]) || $t[0] == '\\')
404
							$s .= ' ';
405
406
						$s .= $t;
407
					}
408
				}
409
			break;
410
411
			case KEYWORD_WITH:
412
				$s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
413
			break;
414
415
			case KEYWORD_VAR:
416
			case KEYWORD_CONST:
417
				$s = $n->value . ' ';
418
				$childs = $n->treeNodes;
419
				for ($i = 0, $j = count($childs); $i < $j; $i++)
420
				{
421
					$t = $childs[$i];
422
					$s .= ($i ? ',' : '') . $t->name;
423
					$u = $t->initializer;
424
					if ($u)
425
						$s .= '=' . $this->parseTree($u);
426
				}
427
			break;
428
429
			case KEYWORD_IN:
430
			case KEYWORD_INSTANCEOF:
431
				$left = $this->parseTree($n->treeNodes[0]);
432
				$right = $this->parseTree($n->treeNodes[1]);
433
434
				$s = $left;
435
436
				if ($this->isWordChar(substr($left, -1)))
437
					$s .= ' ';
438
439
				$s .= $n->type;
440
441
				if ($this->isWordChar($right[0]) || $right[0] == '\\')
442
					$s .= ' ';
443
444
				$s .= $right;
445
			break;
446
447
			case KEYWORD_DELETE:
448
			case KEYWORD_TYPEOF:
449
				$right = $this->parseTree($n->treeNodes[0]);
450
451
				$s = $n->type;
452
453
				if ($this->isWordChar($right[0]) || $right[0] == '\\')
454
					$s .= ' ';
455
456
				$s .= $right;
457
			break;
458
459
			case KEYWORD_VOID:
460
				$s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
461
			break;
462
463
			case KEYWORD_DEBUGGER:
464
				throw new Exception('NOT IMPLEMENTED: DEBUGGER');
465
			break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
466
467
			case TOKEN_CONDCOMMENT_START:
468
			case TOKEN_CONDCOMMENT_END:
469
				$s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
470
				$childs = $n->treeNodes;
471
				for ($i = 0, $j = count($childs); $i < $j; $i++)
472
					$s .= $this->parseTree($childs[$i]);
473
			break;
474
475
			case OP_SEMICOLON:
476
				if ($expression = $n->expression)
477
					$s = $this->parseTree($expression);
478
			break;
479
480
			case JS_LABEL:
481
				$s = $n->label . ':' . $this->parseTree($n->statement);
482
			break;
483
484
			case OP_COMMA:
485
				$childs = $n->treeNodes;
486
				for ($i = 0, $j = count($childs); $i < $j; $i++)
487
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
488
			break;
489
490
			case OP_ASSIGN:
491
				$s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
492
			break;
493
494
			case OP_HOOK:
495
				$s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
496
			break;
497
498
			case OP_OR: case OP_AND:
499
			case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
500
			case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
501
			case OP_LT: case OP_LE: case OP_GE: case OP_GT:
502
			case OP_LSH: case OP_RSH: case OP_URSH:
503
			case OP_MUL: case OP_DIV: case OP_MOD:
504
				$s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
505
			break;
506
507
			case OP_PLUS:
508
			case OP_MINUS:
509
				$left = $this->parseTree($n->treeNodes[0]);
510
				$right = $this->parseTree($n->treeNodes[1]);
511
512
				switch ($n->treeNodes[1]->type)
513
				{
514
					case OP_PLUS:
515
					case OP_MINUS:
516
					case OP_INCREMENT:
517
					case OP_DECREMENT:
518
					case OP_UNARY_PLUS:
519
					case OP_UNARY_MINUS:
520
						$s = $left . $n->type . ' ' . $right;
521
					break;
522
523
					case TOKEN_STRING:
524
						//combine concatted strings with same quotestyle
525
						if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
526
						{
527
							$s = substr($left, 0, -1) . substr($right, 1);
528
							break;
529
						}
530
					// FALL THROUGH
531
532
					default:
533
						$s = $left . $n->type . $right;
534
				}
535
			break;
536
537
			case OP_NOT:
538
			case OP_BITWISE_NOT:
539
			case OP_UNARY_PLUS:
540
			case OP_UNARY_MINUS:
541
				$s = $n->value . $this->parseTree($n->treeNodes[0]);
542
			break;
543
544
			case OP_INCREMENT:
545
			case OP_DECREMENT:
546
				if ($n->postfix)
547
					$s = $this->parseTree($n->treeNodes[0]) . $n->value;
548
				else
549
					$s = $n->value . $this->parseTree($n->treeNodes[0]);
550
			break;
551
552
			case OP_DOT:
553
				$s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
554
			break;
555
556
			case JS_INDEX:
557
				$s = $this->parseTree($n->treeNodes[0]);
558
				// See if we can replace named index with a dot saving 3 bytes
559
				if (	$n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
560
					$n->treeNodes[1]->type == TOKEN_STRING &&
561
					$this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
562
				)
563
					$s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
564
				else
565
					$s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
566
			break;
567
568
			case JS_LIST:
569
				$childs = $n->treeNodes;
570
				for ($i = 0, $j = count($childs); $i < $j; $i++)
571
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
572
			break;
573
574
			case JS_CALL:
575
				$s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
576
			break;
577
578
			case KEYWORD_NEW:
579
			case JS_NEW_WITH_ARGS:
580
				$s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
581
			break;
582
583
			case JS_ARRAY_INIT:
584
				$s = '[';
585
				$childs = $n->treeNodes;
586
				for ($i = 0, $j = count($childs); $i < $j; $i++)
587
				{
588
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
589
				}
590
				$s .= ']';
591
			break;
592
593
			case JS_OBJECT_INIT:
594
				$s = '{';
595
				$childs = $n->treeNodes;
596
				for ($i = 0, $j = count($childs); $i < $j; $i++)
597
				{
598
					$t = $childs[$i];
599
					if ($i)
600
						$s .= ',';
601
					if ($t->type == JS_PROPERTY_INIT)
602
					{
603
						// Ditch the quotes when the index is a valid identifier
604
						if (	$t->treeNodes[0]->type == TOKEN_STRING &&
605
							$this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
606
						)
607
							$s .= substr($t->treeNodes[0]->value, 1, -1);
608
						else
609
							$s .= $t->treeNodes[0]->value;
610
611
						$s .= ':' . $this->parseTree($t->treeNodes[1]);
612
					}
613
					else
614
					{
615
						$s .= $t->type == JS_GETTER ? 'get' : 'set';
616
						$s .= ' ' . $t->name . '(';
617
						$params = $t->params;
618
						for ($i = 0, $j = count($params); $i < $j; $i++)
619
							$s .= ($i ? ',' : '') . $params[$i];
620
						$s .= '){' . $this->parseTree($t->body, true) . '}';
621
					}
622
				}
623
				$s .= '}';
624
			break;
625
626
			case TOKEN_NUMBER:
627
				$s = $n->value;
628
				if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
629
					$s = $m[1] . 'e' . strlen($m[2]);
630
			break;
631
632
			case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
633
			case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
634
				$s = $n->value;
635
			break;
636
637
			case JS_GROUP:
638
				if (in_array(
639
					$n->treeNodes[0]->type,
640
					array(
641
						JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
642
						TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
643
						KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
644
					)
645
				))
646
				{
647
					$s = $this->parseTree($n->treeNodes[0]);
648
				}
649
				else
650
				{
651
					$s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
652
				}
653
			break;
654
655
			default:
656
				throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
657
		}
658
659
		return $s;
660
	}
661
662
	private function isValidIdentifier($string)
663
	{
664
		return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
665
	}
666
667
	private function isWordChar($char)
668
	{
669
		return $char == '_' || $char == '$' || ctype_alnum($char);
670
	}
671
}
672
673
class JSParser
674
{
675
	private $t;
676
	private $minifier;
677
678
	private $opPrecedence = array(
679
		';' => 0,
680
		',' => 1,
681
		'=' => 2, '?' => 2, ':' => 2,
682
		// The above all have to have the same precedence, see bug 330975
683
		'||' => 4,
684
		'&&' => 5,
685
		'|' => 6,
686
		'^' => 7,
687
		'&' => 8,
688
		'==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
689
		'<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
690
		'<<' => 11, '>>' => 11, '>>>' => 11,
691
		'+' => 12, '-' => 12,
692
		'*' => 13, '/' => 13, '%' => 13,
693
		'delete' => 14, 'void' => 14, 'typeof' => 14,
694
		'!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
695
		'++' => 15, '--' => 15,
696
		'new' => 16,
697
		'.' => 17,
698
		JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
699
		JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
700
	);
701
702
	private $opArity = array(
703
		',' => -2,
704
		'=' => 2,
705
		'?' => 3,
706
		'||' => 2,
707
		'&&' => 2,
708
		'|' => 2,
709
		'^' => 2,
710
		'&' => 2,
711
		'==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
712
		'<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
713
		'<<' => 2, '>>' => 2, '>>>' => 2,
714
		'+' => 2, '-' => 2,
715
		'*' => 2, '/' => 2, '%' => 2,
716
		'delete' => 1, 'void' => 1, 'typeof' => 1,
717
		'!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
718
		'++' => 1, '--' => 1,
719
		'new' => 1,
720
		'.' => 2,
721
		JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
722
		JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
723
		TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
724
	);
725
726
	public function __construct($minifier=null)
727
	{
728
		$this->minifier = $minifier;
729
		$this->t = new JSTokenizer();
730
	}
731
732
	public function parse($s, $f, $l)
733
	{
734
		// initialize tokenizer
735
		$this->t->init($s, $f, $l);
736
737
		$x = new JSCompilerContext(false);
738
		$n = $this->Script($x);
739
		if (!$this->t->isDone())
740
			throw $this->t->newSyntaxError('Syntax error');
741
742
		return $n;
743
	}
744
745
	private function Script($x)
746
	{
747
		$n = $this->Statements($x);
748
		$n->type = JS_SCRIPT;
749
		$n->funDecls = $x->funDecls;
750
		$n->varDecls = $x->varDecls;
751
752
		// minify by scope
753
		if ($this->minifier)
754
		{
755
			$n->value = $this->minifier->parseTree($n);
756
757
			// clear tree from node to save memory
758
			$n->treeNodes = null;
759
			$n->funDecls = null;
760
			$n->varDecls = null;
761
762
			$n->type = JS_MINIFIED;
763
		}
764
765
		return $n;
766
	}
767
768
	private function Statements($x)
769
	{
770
		$n = new JSNode($this->t, JS_BLOCK);
771
		array_push($x->stmtStack, $n);
772
773
		while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
774
			$n->addNode($this->Statement($x));
775
776
		array_pop($x->stmtStack);
777
778
		return $n;
779
	}
780
781
	private function Block($x)
782
	{
783
		$this->t->mustMatch(OP_LEFT_CURLY);
784
		$n = $this->Statements($x);
785
		$this->t->mustMatch(OP_RIGHT_CURLY);
786
787
		return $n;
788
	}
789
790
	private function Statement($x)
791
	{
792
		$tt = $this->t->get();
793
		$n2 = null;
794
795
		// Cases for statements ending in a right curly return early, avoiding the
796
		// common semicolon insertion magic after this switch.
797
		switch ($tt)
798
		{
799
			case KEYWORD_FUNCTION:
800
				return $this->FunctionDefinition(
801
					$x,
802
					true,
803
					count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
804
				);
805
			break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
806
807
			case OP_LEFT_CURLY:
808
				$n = $this->Statements($x);
809
				$this->t->mustMatch(OP_RIGHT_CURLY);
810
			return $n;
811
812
			case KEYWORD_IF:
813
				$n = new JSNode($this->t);
814
				$n->condition = $this->ParenExpression($x);
815
				array_push($x->stmtStack, $n);
816
				$n->thenPart = $this->Statement($x);
817
				$n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
818
				array_pop($x->stmtStack);
819
			return $n;
820
821
			case KEYWORD_SWITCH:
822
				$n = new JSNode($this->t);
823
				$this->t->mustMatch(OP_LEFT_PAREN);
824
				$n->discriminant = $this->Expression($x);
825
				$this->t->mustMatch(OP_RIGHT_PAREN);
826
				$n->cases = array();
827
				$n->defaultIndex = -1;
828
829
				array_push($x->stmtStack, $n);
830
831
				$this->t->mustMatch(OP_LEFT_CURLY);
832
833
				while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
834
				{
835
					switch ($tt)
836
					{
837
						case KEYWORD_DEFAULT:
838
							if ($n->defaultIndex >= 0)
839
								throw $this->t->newSyntaxError('More than one switch default');
840
							// FALL THROUGH
841
						case KEYWORD_CASE:
842
							$n2 = new JSNode($this->t);
843
							if ($tt == KEYWORD_DEFAULT)
844
								$n->defaultIndex = count($n->cases);
845
							else
846
								$n2->caseLabel = $this->Expression($x, OP_COLON);
847
								break;
848
						default:
849
							throw $this->t->newSyntaxError('Invalid switch case');
850
					}
851
852
					$this->t->mustMatch(OP_COLON);
853
					$n2->statements = new JSNode($this->t, JS_BLOCK);
854
					while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
855
						$n2->statements->addNode($this->Statement($x));
856
857
					array_push($n->cases, $n2);
858
				}
859
860
				array_pop($x->stmtStack);
861
			return $n;
862
863
			case KEYWORD_FOR:
864
				$n = new JSNode($this->t);
865
				$n->isLoop = true;
866
				$this->t->mustMatch(OP_LEFT_PAREN);
867
868
				if (($tt = $this->t->peek()) != OP_SEMICOLON)
869
				{
870
					$x->inForLoopInit = true;
871
					if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
872
					{
873
						$this->t->get();
874
						$n2 = $this->Variables($x);
875
					}
876
					else
877
					{
878
						$n2 = $this->Expression($x);
879
					}
880
					$x->inForLoopInit = false;
881
				}
882
883
				if ($n2 && $this->t->match(KEYWORD_IN))
884
				{
885
					$n->type = JS_FOR_IN;
886
					if ($n2->type == KEYWORD_VAR)
887
					{
888
						if (count($n2->treeNodes) != 1)
889
						{
890
							throw $this->t->newSyntaxError(
891
								'Invalid for..in left-hand side',
892
								$this->t->filename,
0 ignored issues
show
The call to JSTokenizer::newSyntaxError() has too many arguments starting with $this->t->filename.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
893
								$n2->lineno
894
							);
895
						}
896
897
						// NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
898
						$n->iterator = $n2->treeNodes[0];
899
						$n->varDecl = $n2;
900
					}
901
					else
902
					{
903
						$n->iterator = $n2;
904
						$n->varDecl = null;
905
					}
906
907
					$n->object = $this->Expression($x);
908
				}
909
				else
910
				{
911
					$n->setup = $n2 ? $n2 : null;
912
					$this->t->mustMatch(OP_SEMICOLON);
913
					$n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
914
					$this->t->mustMatch(OP_SEMICOLON);
915
					$n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
916
				}
917
918
				$this->t->mustMatch(OP_RIGHT_PAREN);
919
				$n->body = $this->nest($x, $n);
920
			return $n;
921
922
			case KEYWORD_WHILE:
923
			        $n = new JSNode($this->t);
924
			        $n->isLoop = true;
925
			        $n->condition = $this->ParenExpression($x);
926
			        $n->body = $this->nest($x, $n);
927
			return $n;
928
929
			case KEYWORD_DO:
930
				$n = new JSNode($this->t);
931
				$n->isLoop = true;
932
				$n->body = $this->nest($x, $n, KEYWORD_WHILE);
933
				$n->condition = $this->ParenExpression($x);
934
				if (!$x->ecmaStrictMode)
935
				{
936
					// <script language="JavaScript"> (without version hints) may need
937
					// automatic semicolon insertion without a newline after do-while.
938
					// See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
939
					$this->t->match(OP_SEMICOLON);
940
					return $n;
941
				}
942
			break;
943
944
			case KEYWORD_BREAK:
945
			case KEYWORD_CONTINUE:
946
				$n = new JSNode($this->t);
947
948
				if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
949
				{
950
					$this->t->get();
951
					$n->label = $this->t->currentToken()->value;
952
				}
953
954
				$ss = $x->stmtStack;
955
				$i = count($ss);
956
				$label = $n->label;
957
				if ($label)
958
				{
959
					do
960
					{
961
						if (--$i < 0)
962
							throw $this->t->newSyntaxError('Label not found');
963
					}
964
					while ($ss[$i]->label != $label);
965
				}
966
				else
967
				{
968
					do
969
					{
970
						if (--$i < 0)
971
							throw $this->t->newSyntaxError('Invalid ' . $tt);
972
					}
973
					while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
974
				}
975
976
				$n->target = $ss[$i];
977
			break;
978
979
			case KEYWORD_TRY:
980
				$n = new JSNode($this->t);
981
				$n->tryBlock = $this->Block($x);
982
				$n->catchClauses = array();
983
984
				while ($this->t->match(KEYWORD_CATCH))
985
				{
986
					$n2 = new JSNode($this->t);
987
					$this->t->mustMatch(OP_LEFT_PAREN);
988
					$n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
989
990
					if ($this->t->match(KEYWORD_IF))
991
					{
992
						if ($x->ecmaStrictMode)
993
							throw $this->t->newSyntaxError('Illegal catch guard');
994
995
						if (count($n->catchClauses) && !end($n->catchClauses)->guard)
996
							throw $this->t->newSyntaxError('Guarded catch after unguarded');
997
998
						$n2->guard = $this->Expression($x);
999
					}
1000
					else
1001
					{
1002
						$n2->guard = null;
1003
					}
1004
1005
					$this->t->mustMatch(OP_RIGHT_PAREN);
1006
					$n2->block = $this->Block($x);
1007
					array_push($n->catchClauses, $n2);
1008
				}
1009
1010
				if ($this->t->match(KEYWORD_FINALLY))
1011
					$n->finallyBlock = $this->Block($x);
1012
1013
				if (!count($n->catchClauses) && !$n->finallyBlock)
1014
					throw $this->t->newSyntaxError('Invalid try statement');
1015
			return $n;
1016
1017
			case KEYWORD_CATCH:
1018
			case KEYWORD_FINALLY:
1019
				throw $this->t->newSyntaxError($tt + ' without preceding try');
1020
1021
			case KEYWORD_THROW:
1022
				$n = new JSNode($this->t);
1023
				$n->value = $this->Expression($x);
1024
			break;
1025
1026
			case KEYWORD_RETURN:
1027
				if (!$x->inFunction)
1028
					throw $this->t->newSyntaxError('Invalid return');
1029
1030
				$n = new JSNode($this->t);
1031
				$tt = $this->t->peekOnSameLine();
1032
				if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1033
					$n->value = $this->Expression($x);
1034
				else
1035
					$n->value = null;
1036
			break;
1037
1038
			case KEYWORD_WITH:
1039
				$n = new JSNode($this->t);
1040
				$n->object = $this->ParenExpression($x);
1041
				$n->body = $this->nest($x, $n);
1042
			return $n;
1043
1044
			case KEYWORD_VAR:
1045
			case KEYWORD_CONST:
1046
			        $n = $this->Variables($x);
1047
			break;
1048
1049
			case TOKEN_CONDCOMMENT_START:
1050
			case TOKEN_CONDCOMMENT_END:
1051
				$n = new JSNode($this->t);
1052
			return $n;
1053
1054
			case KEYWORD_DEBUGGER:
1055
				$n = new JSNode($this->t);
1056
			break;
1057
1058
			case TOKEN_NEWLINE:
1059
			case OP_SEMICOLON:
1060
				$n = new JSNode($this->t, OP_SEMICOLON);
1061
				$n->expression = null;
1062
			return $n;
1063
1064
			default:
1065
				if ($tt == TOKEN_IDENTIFIER)
1066
				{
1067
					$this->t->scanOperand = false;
1068
					$tt = $this->t->peek();
1069
					$this->t->scanOperand = true;
1070
					if ($tt == OP_COLON)
1071
					{
1072
						$label = $this->t->currentToken()->value;
1073
						$ss = $x->stmtStack;
1074
						for ($i = count($ss) - 1; $i >= 0; --$i)
1075
						{
1076
							if ($ss[$i]->label == $label)
1077
								throw $this->t->newSyntaxError('Duplicate label');
1078
						}
1079
1080
						$this->t->get();
1081
						$n = new JSNode($this->t, JS_LABEL);
1082
						$n->label = $label;
1083
						$n->statement = $this->nest($x, $n);
1084
1085
						return $n;
1086
					}
1087
				}
1088
1089
				$n = new JSNode($this->t, OP_SEMICOLON);
1090
				$this->t->unget();
1091
				$n->expression = $this->Expression($x);
1092
				$n->end = $n->expression->end;
1093
			break;
1094
		}
1095
1096
		if ($this->t->lineno == $this->t->currentToken()->lineno)
1097
		{
1098
			$tt = $this->t->peekOnSameLine();
1099
			if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1100
				throw $this->t->newSyntaxError('Missing ; before statement');
1101
		}
1102
1103
		$this->t->match(OP_SEMICOLON);
1104
1105
		return $n;
1106
	}
1107
1108
	private function FunctionDefinition($x, $requireName, $functionForm)
1109
	{
1110
		$f = new JSNode($this->t);
1111
1112
		if ($f->type != KEYWORD_FUNCTION)
1113
			$f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1114
1115
		if ($this->t->match(TOKEN_IDENTIFIER))
1116
			$f->name = $this->t->currentToken()->value;
1117
		elseif ($requireName)
1118
			throw $this->t->newSyntaxError('Missing function identifier');
1119
1120
		$this->t->mustMatch(OP_LEFT_PAREN);
1121
			$f->params = array();
1122
1123
		while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1124
		{
1125
			if ($tt != TOKEN_IDENTIFIER)
1126
				throw $this->t->newSyntaxError('Missing formal parameter');
1127
1128
			array_push($f->params, $this->t->currentToken()->value);
1129
1130
			if ($this->t->peek() != OP_RIGHT_PAREN)
1131
				$this->t->mustMatch(OP_COMMA);
1132
		}
1133
1134
		$this->t->mustMatch(OP_LEFT_CURLY);
1135
1136
		$x2 = new JSCompilerContext(true);
1137
		$f->body = $this->Script($x2);
1138
1139
		$this->t->mustMatch(OP_RIGHT_CURLY);
1140
		$f->end = $this->t->currentToken()->end;
1141
1142
		$f->functionForm = $functionForm;
1143
		if ($functionForm == DECLARED_FORM)
1144
			array_push($x->funDecls, $f);
1145
1146
		return $f;
1147
	}
1148
1149
	private function Variables($x)
1150
	{
1151
		$n = new JSNode($this->t);
1152
1153
		do
1154
		{
1155
			$this->t->mustMatch(TOKEN_IDENTIFIER);
1156
1157
			$n2 = new JSNode($this->t);
1158
			$n2->name = $n2->value;
1159
1160
			if ($this->t->match(OP_ASSIGN))
1161
			{
1162
				if ($this->t->currentToken()->assignOp)
1163
					throw $this->t->newSyntaxError('Invalid variable initialization');
1164
1165
				$n2->initializer = $this->Expression($x, OP_COMMA);
1166
			}
1167
1168
			$n2->readOnly = $n->type == KEYWORD_CONST;
1169
1170
			$n->addNode($n2);
1171
			array_push($x->varDecls, $n2);
1172
		}
1173
		while ($this->t->match(OP_COMMA));
1174
1175
		return $n;
1176
	}
1177
1178
	private function Expression($x, $stop=false)
1179
	{
1180
		$operators = array();
1181
		$operands = array();
1182
		$n = false;
0 ignored issues
show
$n is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1183
1184
		$bl = $x->bracketLevel;
1185
		$cl = $x->curlyLevel;
1186
		$pl = $x->parenLevel;
1187
		$hl = $x->hookLevel;
1188
1189
		while (($tt = $this->t->get()) != TOKEN_END)
1190
		{
1191
			if ($tt == $stop &&
1192
				$x->bracketLevel == $bl &&
1193
				$x->curlyLevel == $cl &&
1194
				$x->parenLevel == $pl &&
1195
				$x->hookLevel == $hl
1196
			)
1197
			{
1198
				// Stop only if tt matches the optional stop parameter, and that
1199
				// token is not quoted by some kind of bracket.
1200
				break;
1201
			}
1202
1203
			switch ($tt)
1204
			{
1205
				case OP_SEMICOLON:
1206
					// NB: cannot be empty, Statement handled that.
1207
					break 2;
1208
1209
				case OP_HOOK:
1210
					if ($this->t->scanOperand)
1211
						break 2;
1212
1213
					while (	!empty($operators) &&
1214
						$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1215
					)
1216
						$this->reduce($operators, $operands);
1217
1218
					array_push($operators, new JSNode($this->t));
1219
1220
					++$x->hookLevel;
1221
					$this->t->scanOperand = true;
1222
					$n = $this->Expression($x);
1223
1224
					if (!$this->t->match(OP_COLON))
1225
						break 2;
1226
1227
					--$x->hookLevel;
1228
					array_push($operands, $n);
1229
				break;
1230
1231
				case OP_COLON:
1232
					if ($x->hookLevel)
1233
						break 2;
1234
1235
					throw $this->t->newSyntaxError('Invalid label');
1236
				break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1237
1238
				case OP_ASSIGN:
1239
					if ($this->t->scanOperand)
1240
						break 2;
1241
1242
					// Use >, not >=, for right-associative ASSIGN
1243
					while (	!empty($operators) &&
1244
						$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1245
					)
1246
						$this->reduce($operators, $operands);
1247
1248
					array_push($operators, new JSNode($this->t));
1249
					end($operands)->assignOp = $this->t->currentToken()->assignOp;
1250
					$this->t->scanOperand = true;
1251
				break;
1252
1253
				case KEYWORD_IN:
1254
					// An in operator should not be parsed if we're parsing the head of
1255
					// a for (...) loop, unless it is in the then part of a conditional
1256
					// expression, or parenthesized somehow.
1257
					if ($x->inForLoopInit && !$x->hookLevel &&
1258
						!$x->bracketLevel && !$x->curlyLevel &&
1259
						!$x->parenLevel
1260
					)
1261
						break 2;
1262
				// FALL THROUGH
1263
				case OP_COMMA:
1264
					// A comma operator should not be parsed if we're parsing the then part
1265
					// of a conditional expression unless it's parenthesized somehow.
1266
					if ($tt == OP_COMMA && $x->hookLevel &&
1267
						!$x->bracketLevel && !$x->curlyLevel &&
1268
						!$x->parenLevel
1269
					)
1270
						break 2;
1271
				// Treat comma as left-associative so reduce can fold left-heavy
1272
				// COMMA trees into a single array.
1273
				// FALL THROUGH
1274
				case OP_OR:
1275
				case OP_AND:
1276
				case OP_BITWISE_OR:
1277
				case OP_BITWISE_XOR:
1278
				case OP_BITWISE_AND:
1279
				case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1280
				case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1281
				case KEYWORD_INSTANCEOF:
1282
				case OP_LSH: case OP_RSH: case OP_URSH:
1283
				case OP_PLUS: case OP_MINUS:
1284
				case OP_MUL: case OP_DIV: case OP_MOD:
1285
				case OP_DOT:
1286
					if ($this->t->scanOperand)
1287
						break 2;
1288
1289
					while (	!empty($operators) &&
1290
						$this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1291
					)
1292
						$this->reduce($operators, $operands);
1293
1294
					if ($tt == OP_DOT)
1295
					{
1296
						$this->t->mustMatch(TOKEN_IDENTIFIER);
1297
						array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1298
					}
1299
					else
1300
					{
1301
						array_push($operators, new JSNode($this->t));
1302
						$this->t->scanOperand = true;
1303
					}
1304
				break;
1305
1306
				case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1307
				case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1308
				case KEYWORD_NEW:
1309
					if (!$this->t->scanOperand)
1310
						break 2;
1311
1312
					array_push($operators, new JSNode($this->t));
1313
				break;
1314
1315
				case OP_INCREMENT: case OP_DECREMENT:
1316
					if ($this->t->scanOperand)
1317
					{
1318
						array_push($operators, new JSNode($this->t));  // prefix increment or decrement
1319
					}
1320
					else
1321
					{
1322
						// Don't cross a line boundary for postfix {in,de}crement.
1323
						$t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1324
						if ($t && $t->lineno != $this->t->lineno)
1325
							break 2;
1326
1327
						if (!empty($operators))
1328
						{
1329
							// Use >, not >=, so postfix has higher precedence than prefix.
1330
							while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1331
								$this->reduce($operators, $operands);
1332
						}
1333
1334
						$n = new JSNode($this->t, $tt, array_pop($operands));
1335
						$n->postfix = true;
1336
						array_push($operands, $n);
1337
					}
1338
				break;
1339
1340
				case KEYWORD_FUNCTION:
1341
					if (!$this->t->scanOperand)
1342
						break 2;
1343
1344
					array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1345
					$this->t->scanOperand = false;
1346
				break;
1347
1348
				case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1349
				case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1350
					if (!$this->t->scanOperand)
1351
						break 2;
1352
1353
					array_push($operands, new JSNode($this->t));
1354
					$this->t->scanOperand = false;
1355
				break;
1356
1357
				case TOKEN_CONDCOMMENT_START:
1358
				case TOKEN_CONDCOMMENT_END:
1359
					if ($this->t->scanOperand)
1360
						array_push($operators, new JSNode($this->t));
1361
					else
1362
						array_push($operands, new JSNode($this->t));
1363
				break;
1364
1365
				case OP_LEFT_BRACKET:
1366
					if ($this->t->scanOperand)
1367
					{
1368
						// Array initialiser.  Parse using recursive descent, as the
1369
						// sub-grammar here is not an operator grammar.
1370
						$n = new JSNode($this->t, JS_ARRAY_INIT);
1371
						while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1372
						{
1373
							if ($tt == OP_COMMA)
1374
							{
1375
								$this->t->get();
1376
								$n->addNode(null);
1377
								continue;
1378
							}
1379
1380
							$n->addNode($this->Expression($x, OP_COMMA));
1381
							if (!$this->t->match(OP_COMMA))
1382
								break;
1383
						}
1384
1385
						$this->t->mustMatch(OP_RIGHT_BRACKET);
1386
						array_push($operands, $n);
1387
						$this->t->scanOperand = false;
1388
					}
1389
					else
1390
					{
1391
						// Property indexing operator.
1392
						array_push($operators, new JSNode($this->t, JS_INDEX));
1393
						$this->t->scanOperand = true;
1394
						++$x->bracketLevel;
1395
					}
1396
				break;
1397
1398
				case OP_RIGHT_BRACKET:
1399
					if ($this->t->scanOperand || $x->bracketLevel == $bl)
1400
						break 2;
1401
1402
					while ($this->reduce($operators, $operands)->type != JS_INDEX)
1403
						continue;
1404
1405
					--$x->bracketLevel;
1406
				break;
1407
1408
				case OP_LEFT_CURLY:
1409
					if (!$this->t->scanOperand)
1410
						break 2;
1411
1412
					// Object initialiser.  As for array initialisers (see above),
1413
					// parse using recursive descent.
1414
					++$x->curlyLevel;
1415
					$n = new JSNode($this->t, JS_OBJECT_INIT);
1416
					while (!$this->t->match(OP_RIGHT_CURLY))
1417
					{
1418
						do
1419
						{
1420
							$tt = $this->t->get();
1421
							$tv = $this->t->currentToken()->value;
1422
							if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1423
							{
1424
								if ($x->ecmaStrictMode)
1425
									throw $this->t->newSyntaxError('Illegal property accessor');
1426
1427
								$n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1428
							}
1429
							else
1430
							{
1431
								switch ($tt)
1432
								{
1433
									case TOKEN_IDENTIFIER:
1434
									case TOKEN_NUMBER:
1435
									case TOKEN_STRING:
1436
										$id = new JSNode($this->t);
1437
									break;
1438
1439
									case OP_RIGHT_CURLY:
1440
										if ($x->ecmaStrictMode)
1441
											throw $this->t->newSyntaxError('Illegal trailing ,');
1442
									break 3;
1443
1444
									default:
1445
										throw $this->t->newSyntaxError('Invalid property name');
1446
								}
1447
1448
								$this->t->mustMatch(OP_COLON);
1449
								$n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1450
							}
1451
						}
1452
						while ($this->t->match(OP_COMMA));
1453
1454
						$this->t->mustMatch(OP_RIGHT_CURLY);
1455
						break;
1456
					}
1457
1458
					array_push($operands, $n);
1459
					$this->t->scanOperand = false;
1460
					--$x->curlyLevel;
1461
				break;
1462
1463
				case OP_RIGHT_CURLY:
1464
					if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1465
						throw new Exception('PANIC: right curly botch');
1466
				break 2;
1467
1468
				case OP_LEFT_PAREN:
1469
					if ($this->t->scanOperand)
1470
					{
1471
						array_push($operators, new JSNode($this->t, JS_GROUP));
1472
					}
1473
					else
1474
					{
1475
						while (	!empty($operators) &&
1476
							$this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1477
						)
1478
							$this->reduce($operators, $operands);
1479
1480
						// Handle () now, to regularize the n-ary case for n > 0.
1481
						// We must set scanOperand in case there are arguments and
1482
						// the first one is a regexp or unary+/-.
1483
						$n = end($operators);
1484
						$this->t->scanOperand = true;
1485
						if ($this->t->match(OP_RIGHT_PAREN))
1486
						{
1487
							if ($n && $n->type == KEYWORD_NEW)
1488
							{
1489
								array_pop($operators);
1490
								$n->addNode(array_pop($operands));
1491
							}
1492
							else
1493
							{
1494
								$n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1495
							}
1496
1497
							array_push($operands, $n);
1498
							$this->t->scanOperand = false;
1499
							break;
1500
						}
1501
1502
						if ($n && $n->type == KEYWORD_NEW)
1503
							$n->type = JS_NEW_WITH_ARGS;
1504
						else
1505
							array_push($operators, new JSNode($this->t, JS_CALL));
1506
					}
1507
1508
					++$x->parenLevel;
1509
				break;
1510
1511
				case OP_RIGHT_PAREN:
1512
					if ($this->t->scanOperand || $x->parenLevel == $pl)
1513
						break 2;
1514
1515
					while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1516
						$tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1517
					)
1518
					{
1519
						continue;
1520
					}
1521
1522
					if ($tt != JS_GROUP)
1523
					{
1524
						$n = end($operands);
1525
						if ($n->treeNodes[1]->type != OP_COMMA)
1526
							$n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1527
						else
1528
							$n->treeNodes[1]->type = JS_LIST;
1529
					}
1530
1531
					--$x->parenLevel;
1532
				break;
1533
1534
				// Automatic semicolon insertion means we may scan across a newline
1535
				// and into the beginning of another statement.  If so, break out of
1536
				// the while loop and let the t.scanOperand logic handle errors.
1537
				default:
1538
					break 2;
1539
			}
1540
		}
1541
1542
		if ($x->hookLevel != $hl)
1543
			throw $this->t->newSyntaxError('Missing : in conditional expression');
1544
1545
		if ($x->parenLevel != $pl)
1546
			throw $this->t->newSyntaxError('Missing ) in parenthetical');
1547
1548
		if ($x->bracketLevel != $bl)
1549
			throw $this->t->newSyntaxError('Missing ] in index expression');
1550
1551
		if ($this->t->scanOperand)
1552
			throw $this->t->newSyntaxError('Missing operand');
1553
1554
		// Resume default mode, scanning for operands, not operators.
1555
		$this->t->scanOperand = true;
1556
		$this->t->unget();
1557
1558
		while (count($operators))
1559
			$this->reduce($operators, $operands);
1560
1561
		return array_pop($operands);
1562
	}
1563
1564
	private function ParenExpression($x)
1565
	{
1566
		$this->t->mustMatch(OP_LEFT_PAREN);
1567
		$n = $this->Expression($x);
1568
		$this->t->mustMatch(OP_RIGHT_PAREN);
1569
1570
		return $n;
1571
	}
1572
1573
	// Statement stack and nested statement handler.
1574
	private function nest($x, $node, $end = false)
1575
	{
1576
		array_push($x->stmtStack, $node);
1577
		$n = $this->statement($x);
1578
		array_pop($x->stmtStack);
1579
1580
		if ($end)
1581
			$this->t->mustMatch($end);
1582
1583
		return $n;
1584
	}
1585
1586
	private function reduce(&$operators, &$operands)
1587
	{
1588
		$n = array_pop($operators);
1589
		$op = $n->type;
1590
		$arity = $this->opArity[$op];
1591
		$c = count($operands);
1592
		if ($arity == -2)
1593
		{
1594
			// Flatten left-associative trees
1595
			if ($c >= 2)
1596
			{
1597
				$left = $operands[$c - 2];
1598
				if ($left->type == $op)
1599
				{
1600
					$right = array_pop($operands);
1601
					$left->addNode($right);
1602
					return $left;
1603
				}
1604
			}
1605
			$arity = 2;
1606
		}
1607
1608
		// Always use push to add operands to n, to update start and end
1609
		$a = array_splice($operands, $c - $arity);
1610
		for ($i = 0; $i < $arity; $i++)
1611
			$n->addNode($a[$i]);
1612
1613
		// Include closing bracket or postfix operator in [start,end]
1614
		$te = $this->t->currentToken()->end;
1615
		if ($n->end < $te)
1616
			$n->end = $te;
1617
1618
		array_push($operands, $n);
1619
1620
		return $n;
1621
	}
1622
}
1623
1624
class JSCompilerContext
1625
{
1626
	public $inFunction = false;
1627
	public $inForLoopInit = false;
1628
	public $ecmaStrictMode = false;
1629
	public $bracketLevel = 0;
1630
	public $curlyLevel = 0;
1631
	public $parenLevel = 0;
1632
	public $hookLevel = 0;
1633
1634
	public $stmtStack = array();
1635
	public $funDecls = array();
1636
	public $varDecls = array();
1637
1638
	public function __construct($inFunction)
1639
	{
1640
		$this->inFunction = $inFunction;
1641
	}
1642
}
1643
1644
class JSNode
1645
{
1646
	private $type;
1647
	private $value;
1648
	private $lineno;
1649
	private $start;
1650
	private $end;
1651
1652
	public $treeNodes = array();
1653
	public $funDecls = array();
1654
	public $varDecls = array();
1655
1656
	public function __construct($t, $type=0)
1657
	{
1658
		if ($token = $t->currentToken())
1659
		{
1660
			$this->type = $type ? $type : $token->type;
1661
			$this->value = $token->value;
1662
			$this->lineno = $token->lineno;
1663
			$this->start = $token->start;
1664
			$this->end = $token->end;
1665
		}
1666
		else
1667
		{
1668
			$this->type = $type;
1669
			$this->lineno = $t->lineno;
1670
		}
1671
1672
		if (($numargs = func_num_args()) > 2)
1673
		{
1674
			$args = func_get_args();
1675
			for ($i = 2; $i < $numargs; $i++)
1676
				$this->addNode($args[$i]);
1677
		}
1678
	}
1679
1680
	// we don't want to bloat our object with all kind of specific properties, so we use overloading
1681
	public function __set($name, $value)
1682
	{
1683
		$this->$name = $value;
1684
	}
1685
1686
	public function __get($name)
1687
	{
1688
		if (isset($this->$name))
1689
			return $this->$name;
1690
1691
		return null;
1692
	}
1693
1694
	public function addNode($node)
1695
	{
1696
		if ($node !== null)
1697
		{
1698
			if ($node->start < $this->start)
1699
				$this->start = $node->start;
1700
			if ($this->end < $node->end)
1701
				$this->end = $node->end;
1702
		}
1703
1704
		$this->treeNodes[] = $node;
1705
	}
1706
}
1707
1708
class JSTokenizer
1709
{
1710
	private $cursor = 0;
1711
	private $source;
1712
1713
	public $tokens = array();
1714
	public $tokenIndex = 0;
1715
	public $lookahead = 0;
1716
	public $scanNewlines = false;
1717
	public $scanOperand = true;
1718
1719
	public $filename;
1720
	public $lineno;
1721
1722
	private $keywords = array(
1723
		'break',
1724
		'case', 'catch', 'const', 'continue',
1725
		'debugger', 'default', 'delete', 'do',
1726
		'else', 'enum',
1727
		'false', 'finally', 'for', 'function',
1728
		'if', 'in', 'instanceof',
1729
		'new', 'null',
1730
		'return',
1731
		'switch',
1732
		'this', 'throw', 'true', 'try', 'typeof',
1733
		'var', 'void',
1734
		'while', 'with'
1735
	);
1736
1737
	private $opTypeNames = array(
1738
		';', ',', '?', ':', '||', '&&', '|', '^',
1739
		'&', '===', '==', '=', '!==', '!=', '<<', '<=',
1740
		'<', '>>>', '>>', '>=', '>', '++', '--', '+',
1741
		'-', '*', '/', '%', '!', '~', '.', '[',
1742
		']', '{', '}', '(', ')', '@*/'
1743
	);
1744
1745
	private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1746
	private $opRegExp;
1747
1748
	public function __construct()
1749
	{
1750
		$this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1751
	}
1752
1753
	public function init($source, $filename = '', $lineno = 1)
1754
	{
1755
		$this->source = $source;
1756
		$this->filename = $filename ? $filename : '[inline]';
1757
		$this->lineno = $lineno;
1758
1759
		$this->cursor = 0;
1760
		$this->tokens = array();
1761
		$this->tokenIndex = 0;
1762
		$this->lookahead = 0;
1763
		$this->scanNewlines = false;
1764
		$this->scanOperand = true;
1765
	}
1766
1767
	public function getInput($chunksize)
1768
	{
1769
		if ($chunksize)
1770
			return substr($this->source, $this->cursor, $chunksize);
1771
1772
		return substr($this->source, $this->cursor);
1773
	}
1774
1775
	public function isDone()
1776
	{
1777
		return $this->peek() == TOKEN_END;
1778
	}
1779
1780
	public function match($tt)
1781
	{
1782
		return $this->get() == $tt || $this->unget();
1783
	}
1784
1785
	public function mustMatch($tt)
1786
	{
1787
	        if (!$this->match($tt))
1788
			throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1789
1790
		return $this->currentToken();
1791
	}
1792
1793
	public function peek()
1794
	{
1795
		if ($this->lookahead)
1796
		{
1797
			$next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1798
			if ($this->scanNewlines && $next->lineno != $this->lineno)
1799
				$tt = TOKEN_NEWLINE;
1800
			else
1801
				$tt = $next->type;
1802
		}
1803
		else
1804
		{
1805
			$tt = $this->get();
1806
			$this->unget();
1807
		}
1808
1809
		return $tt;
1810
	}
1811
1812
	public function peekOnSameLine()
1813
	{
1814
		$this->scanNewlines = true;
1815
		$tt = $this->peek();
1816
		$this->scanNewlines = false;
1817
1818
		return $tt;
1819
	}
1820
1821
	public function currentToken()
1822
	{
1823
		if (!empty($this->tokens))
1824
			return $this->tokens[$this->tokenIndex];
1825
	}
1826
1827
	public function get($chunksize = 1000)
1828
	{
1829
		while($this->lookahead)
1830
		{
1831
			$this->lookahead--;
1832
			$this->tokenIndex = ($this->tokenIndex + 1) & 3;
1833
			$token = $this->tokens[$this->tokenIndex];
1834
			if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1835
				return $token->type;
1836
		}
1837
1838
		$conditional_comment = false;
1839
1840
		// strip whitespace and comments
1841
		while(true)
1842
		{
1843
			$input = $this->getInput($chunksize);
1844
1845
			// whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1846
			$re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1847
			if (preg_match($re, $input, $match))
1848
			{
1849
				$spaces = $match[0];
1850
				$spacelen = strlen($spaces);
1851
				$this->cursor += $spacelen;
1852
				if (!$this->scanNewlines)
1853
					$this->lineno += substr_count($spaces, "\n");
1854
1855
				if ($spacelen == $chunksize)
1856
					continue; // complete chunk contained whitespace
1857
1858
				$input = $this->getInput($chunksize);
1859
				if ($input == '' || $input[0] != '/')
1860
					break;
1861
			}
1862
1863
			// Comments
1864
			if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1865
			{
1866
				if (!$chunksize)
1867
					break;
1868
1869
				// retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1870
				$chunksize = null;
1871
				continue;
1872
			}
1873
1874
			// check if this is a conditional (JScript) comment
1875
			if (!empty($match[1]))
1876
			{
1877
				$match[0] = '/*' . $match[1];
1878
				$conditional_comment = true;
1879
				break;
1880
			}
1881
			else
1882
			{
1883
				$this->cursor += strlen($match[0]);
1884
				$this->lineno += substr_count($match[0], "\n");
1885
			}
1886
		}
1887
1888
		if ($input == '')
1889
		{
1890
			$tt = TOKEN_END;
1891
			$match = array('');
1892
		}
1893
		elseif ($conditional_comment)
1894
		{
1895
			$tt = TOKEN_CONDCOMMENT_START;
1896
		}
1897
		else
1898
		{
1899
			switch ($input[0])
1900
			{
1901
				case '0':
1902
					// hexadecimal
1903
					if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1904
					{
1905
						$tt = TOKEN_NUMBER;
1906
						break;
1907
					}
1908
				// FALL THROUGH
1909
1910
				case '1': case '2': case '3': case '4': case '5':
1911
				case '6': case '7': case '8': case '9':
1912
					// should always match
1913
					preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1914
					$tt = TOKEN_NUMBER;
1915
				break;
1916
1917
				case "'":
1918
					if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
1919
					{
1920
						$tt = TOKEN_STRING;
1921
					}
1922
					else
1923
					{
1924
						if ($chunksize)
1925
							return $this->get(null); // retry with a full chunk fetch
1926
1927
						throw $this->newSyntaxError('Unterminated string literal');
1928
					}
1929
				break;
1930
1931
				case '"':
1932
					if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
1933
					{
1934
						$tt = TOKEN_STRING;
1935
					}
1936
					else
1937
					{
1938
						if ($chunksize)
1939
							return $this->get(null); // retry with a full chunk fetch
1940
1941
						throw $this->newSyntaxError('Unterminated string literal');
1942
					}
1943
				break;
1944
1945
				case '/':
1946
					if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1947
					{
1948
						$tt = TOKEN_REGEXP;
1949
						break;
1950
					}
1951
				// FALL THROUGH
1952
1953
				case '|':
1954
				case '^':
1955
				case '&':
1956
				case '<':
1957
				case '>':
1958
				case '+':
1959
				case '-':
1960
				case '*':
1961
				case '%':
1962
				case '=':
1963
				case '!':
1964
					// should always match
1965
					preg_match($this->opRegExp, $input, $match);
1966
					$op = $match[0];
1967
					if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1968
					{
1969
						$tt = OP_ASSIGN;
1970
						$match[0] .= '=';
1971
					}
1972
					else
1973
					{
1974
						$tt = $op;
1975
						if ($this->scanOperand)
1976
						{
1977
							if ($op == OP_PLUS)
1978
								$tt = OP_UNARY_PLUS;
1979
							elseif ($op == OP_MINUS)
1980
								$tt = OP_UNARY_MINUS;
1981
						}
1982
						$op = null;
1983
					}
1984
				break;
1985
1986
				case '.':
1987
					if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1988
					{
1989
						$tt = TOKEN_NUMBER;
1990
						break;
1991
					}
1992
				// FALL THROUGH
1993
1994
				case ';':
1995
				case ',':
1996
				case '?':
1997
				case ':':
1998
				case '~':
1999
				case '[':
2000
				case ']':
2001
				case '{':
2002
				case '}':
2003
				case '(':
2004
				case ')':
2005
					// these are all single
2006
					$match = array($input[0]);
2007
					$tt = $input[0];
2008
				break;
2009
2010
				case '@':
2011
					// check end of conditional comment
2012
					if (substr($input, 0, 3) == '@*/')
2013
					{
2014
						$match = array('@*/');
2015
						$tt = TOKEN_CONDCOMMENT_END;
2016
					}
2017
					else
2018
						throw $this->newSyntaxError('Illegal token');
2019
				break;
2020
2021
				case "\n":
2022
					if ($this->scanNewlines)
2023
					{
2024
						$match = array("\n");
2025
						$tt = TOKEN_NEWLINE;
2026
					}
2027
					else
2028
						throw $this->newSyntaxError('Illegal token');
2029
				break;
2030
2031
				default:
2032
					// FIXME: add support for unicode and unicode escape sequence \uHHHH
2033
					if (preg_match('/^[$\w]+/', $input, $match))
2034
					{
2035
						$tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2036
					}
2037
					else
2038
						throw $this->newSyntaxError('Illegal token');
2039
			}
2040
		}
2041
2042
		$this->tokenIndex = ($this->tokenIndex + 1) & 3;
2043
2044
		if (!isset($this->tokens[$this->tokenIndex]))
2045
			$this->tokens[$this->tokenIndex] = new JSToken();
2046
2047
		$token = $this->tokens[$this->tokenIndex];
2048
		$token->type = $tt;
2049
2050
		if ($tt == OP_ASSIGN)
2051
			$token->assignOp = $op;
2052
2053
		$token->start = $this->cursor;
2054
2055
		$token->value = $match[0];
2056
		$this->cursor += strlen($match[0]);
2057
2058
		$token->end = $this->cursor;
2059
		$token->lineno = $this->lineno;
2060
2061
		return $tt;
2062
	}
2063
2064
	public function unget()
2065
	{
2066
		if (++$this->lookahead == 4)
2067
			throw $this->newSyntaxError('PANIC: too much lookahead!');
2068
2069
		$this->tokenIndex = ($this->tokenIndex - 1) & 3;
2070
	}
2071
2072
	public function newSyntaxError($m)
2073
	{
2074
		return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2075
	}
2076
}
2077
2078
class JSToken
2079
{
2080
	public $type;
2081
	public $value;
2082
	public $start;
2083
	public $end;
2084
	public $lineno;
2085
	public $assignOp;
2086
}
2087