tokenizer.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Copyright (c) The PHP Group |
  4. +----------------------------------------------------------------------+
  5. | This source file is subject to version 3.01 of the PHP license, |
  6. | that is bundled with this package in the file LICENSE, and is |
  7. | available through the world-wide-web at the following url: |
  8. | https://www.php.net/license/3_01.txt |
  9. | If you did not receive a copy of the PHP license and are unable to |
  10. | obtain it through the world-wide-web, please send a note to |
  11. | license@php.net so we can mail you a copy immediately. |
  12. +----------------------------------------------------------------------+
  13. | Author: Andrei Zmievski <andrei@php.net> |
  14. +----------------------------------------------------------------------+
  15. */
  16. #ifdef HAVE_CONFIG_H
  17. #include "config.h"
  18. #endif
  19. #include "php.h"
  20. #include "php_ini.h"
  21. #include "ext/standard/info.h"
  22. #include "php_tokenizer.h"
  23. #include "tokenizer_arginfo.h"
  24. #include "zend.h"
  25. #include "zend_exceptions.h"
  26. #include "zend_language_scanner.h"
  27. #include "zend_language_scanner_defs.h"
  28. #include <zend_language_parser.h>
  29. #include "zend_interfaces.h"
  30. #define zendtext LANG_SCNG(yy_text)
  31. #define zendleng LANG_SCNG(yy_leng)
  32. #define zendcursor LANG_SCNG(yy_cursor)
  33. #define zendlimit LANG_SCNG(yy_limit)
  34. #define TOKEN_PARSE (1 << 0)
  35. zend_class_entry *php_token_ce;
  36. void tokenizer_token_get_all_register_constants(INIT_FUNC_ARGS) {
  37. REGISTER_LONG_CONSTANT("TOKEN_PARSE", TOKEN_PARSE, CONST_CS|CONST_PERSISTENT);
  38. }
  39. /* {{{ tokenizer_module_entry */
  40. zend_module_entry tokenizer_module_entry = {
  41. STANDARD_MODULE_HEADER,
  42. "tokenizer",
  43. ext_functions,
  44. PHP_MINIT(tokenizer),
  45. NULL,
  46. NULL,
  47. NULL,
  48. PHP_MINFO(tokenizer),
  49. PHP_TOKENIZER_VERSION,
  50. STANDARD_MODULE_PROPERTIES
  51. };
  52. /* }}} */
  53. #ifdef COMPILE_DL_TOKENIZER
  54. ZEND_GET_MODULE(tokenizer)
  55. #endif
  56. static zval *php_token_get_id(zval *obj) {
  57. zval *id = OBJ_PROP_NUM(Z_OBJ_P(obj), 0);
  58. if (Z_ISUNDEF_P(id)) {
  59. zend_throw_error(NULL,
  60. "Typed property PhpToken::$id must not be accessed before initialization");
  61. return NULL;
  62. }
  63. ZVAL_DEREF(id);
  64. ZEND_ASSERT(Z_TYPE_P(id) == IS_LONG);
  65. return id;
  66. }
  67. static zend_string *php_token_get_text(zval *obj) {
  68. zval *text_zval = OBJ_PROP_NUM(Z_OBJ_P(obj), 1);
  69. if (Z_ISUNDEF_P(text_zval)) {
  70. zend_throw_error(NULL,
  71. "Typed property PhpToken::$text must not be accessed before initialization");
  72. return NULL;
  73. }
  74. ZVAL_DEREF(text_zval);
  75. ZEND_ASSERT(Z_TYPE_P(text_zval) == IS_STRING);
  76. return Z_STR_P(text_zval);
  77. }
  78. static bool tokenize_common(
  79. zval *return_value, zend_string *source, zend_long flags, zend_class_entry *token_class);
  80. PHP_METHOD(PhpToken, tokenize)
  81. {
  82. zend_string *source;
  83. zend_long flags = 0;
  84. zend_class_entry *token_class;
  85. ZEND_PARSE_PARAMETERS_START(1, 2)
  86. Z_PARAM_STR(source)
  87. Z_PARAM_OPTIONAL
  88. Z_PARAM_LONG(flags)
  89. ZEND_PARSE_PARAMETERS_END();
  90. token_class = zend_get_called_scope(execute_data);
  91. /* Check construction preconditions in advance, so these are not repeated for each token. */
  92. if (token_class->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
  93. zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(token_class->name));
  94. RETURN_THROWS();
  95. }
  96. if (zend_update_class_constants(token_class) == FAILURE) {
  97. RETURN_THROWS();
  98. }
  99. if (!tokenize_common(return_value, source, flags, token_class)) {
  100. RETURN_THROWS();
  101. }
  102. }
  103. PHP_METHOD(PhpToken, __construct)
  104. {
  105. zend_long id;
  106. zend_string *text;
  107. zend_long line = -1;
  108. zend_long pos = -1;
  109. zend_object *obj = Z_OBJ_P(ZEND_THIS);
  110. ZEND_PARSE_PARAMETERS_START(2, 4)
  111. Z_PARAM_LONG(id)
  112. Z_PARAM_STR(text)
  113. Z_PARAM_OPTIONAL
  114. Z_PARAM_LONG(line)
  115. Z_PARAM_LONG(pos)
  116. ZEND_PARSE_PARAMETERS_END();
  117. ZVAL_LONG(OBJ_PROP_NUM(obj, 0), id);
  118. zval_ptr_dtor(OBJ_PROP_NUM(obj, 1));
  119. ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 1), text);
  120. ZVAL_LONG(OBJ_PROP_NUM(obj, 2), line);
  121. ZVAL_LONG(OBJ_PROP_NUM(obj, 3), pos);
  122. }
  123. PHP_METHOD(PhpToken, is)
  124. {
  125. zval *kind;
  126. ZEND_PARSE_PARAMETERS_START(1, 1)
  127. Z_PARAM_ZVAL(kind)
  128. ZEND_PARSE_PARAMETERS_END();
  129. if (Z_TYPE_P(kind) == IS_LONG) {
  130. zval *id_zval = php_token_get_id(ZEND_THIS);
  131. if (!id_zval) {
  132. RETURN_THROWS();
  133. }
  134. RETURN_BOOL(Z_LVAL_P(id_zval) == Z_LVAL_P(kind));
  135. } else if (Z_TYPE_P(kind) == IS_STRING) {
  136. zend_string *text = php_token_get_text(ZEND_THIS);
  137. if (!text) {
  138. RETURN_THROWS();
  139. }
  140. RETURN_BOOL(zend_string_equals(text, Z_STR_P(kind)));
  141. } else if (Z_TYPE_P(kind) == IS_ARRAY) {
  142. zval *id_zval = NULL, *entry;
  143. zend_string *text = NULL;
  144. ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(kind), entry) {
  145. ZVAL_DEREF(entry);
  146. if (Z_TYPE_P(entry) == IS_LONG) {
  147. if (!id_zval) {
  148. id_zval = php_token_get_id(ZEND_THIS);
  149. if (!id_zval) {
  150. RETURN_THROWS();
  151. }
  152. }
  153. if (Z_LVAL_P(id_zval) == Z_LVAL_P(entry)) {
  154. RETURN_TRUE;
  155. }
  156. } else if (Z_TYPE_P(entry) == IS_STRING) {
  157. if (!text) {
  158. text = php_token_get_text(ZEND_THIS);
  159. if (!text) {
  160. RETURN_THROWS();
  161. }
  162. }
  163. if (zend_string_equals(text, Z_STR_P(entry))) {
  164. RETURN_TRUE;
  165. }
  166. } else {
  167. zend_argument_type_error(1, "must only have elements of type string|int, %s given", zend_zval_type_name(entry));
  168. RETURN_THROWS();
  169. }
  170. } ZEND_HASH_FOREACH_END();
  171. RETURN_FALSE;
  172. } else {
  173. zend_argument_type_error(1, "must be of type string|int|array, %s given", zend_zval_type_name(kind));
  174. RETURN_THROWS();
  175. }
  176. }
  177. PHP_METHOD(PhpToken, isIgnorable)
  178. {
  179. ZEND_PARSE_PARAMETERS_NONE();
  180. zval *id_zval = php_token_get_id(ZEND_THIS);
  181. if (!id_zval) {
  182. RETURN_THROWS();
  183. }
  184. zend_long id = Z_LVAL_P(id_zval);
  185. RETURN_BOOL(id == T_WHITESPACE || id == T_COMMENT || id == T_DOC_COMMENT || id == T_OPEN_TAG);
  186. }
  187. PHP_METHOD(PhpToken, getTokenName)
  188. {
  189. ZEND_PARSE_PARAMETERS_NONE();
  190. zval *id_zval = php_token_get_id(ZEND_THIS);
  191. if (!id_zval) {
  192. RETURN_THROWS();
  193. }
  194. if (Z_LVAL_P(id_zval) < 256) {
  195. RETURN_CHAR(Z_LVAL_P(id_zval));
  196. } else {
  197. const char *token_name = get_token_type_name(Z_LVAL_P(id_zval));
  198. if (!token_name) {
  199. RETURN_NULL();
  200. }
  201. RETURN_STRING(token_name);
  202. }
  203. }
  204. PHP_METHOD(PhpToken, __toString)
  205. {
  206. ZEND_PARSE_PARAMETERS_NONE();
  207. zend_string *text = php_token_get_text(ZEND_THIS);
  208. if (!text) {
  209. RETURN_THROWS();
  210. }
  211. RETURN_STR_COPY(text);
  212. }
  213. /* {{{ PHP_MINIT_FUNCTION */
  214. PHP_MINIT_FUNCTION(tokenizer)
  215. {
  216. tokenizer_register_constants(INIT_FUNC_ARGS_PASSTHRU);
  217. tokenizer_token_get_all_register_constants(INIT_FUNC_ARGS_PASSTHRU);
  218. php_token_ce = register_class_PhpToken(zend_ce_stringable);
  219. return SUCCESS;
  220. }
  221. /* }}} */
  222. /* {{{ PHP_MINFO_FUNCTION */
  223. PHP_MINFO_FUNCTION(tokenizer)
  224. {
  225. php_info_print_table_start();
  226. php_info_print_table_row(2, "Tokenizer Support", "enabled");
  227. php_info_print_table_end();
  228. }
  229. /* }}} */
  230. static zend_string *make_str(unsigned char *text, size_t leng, HashTable *interned_strings) {
  231. if (leng == 1) {
  232. return ZSTR_CHAR(text[0]);
  233. } else if (interned_strings) {
  234. zend_string *interned_str = zend_hash_str_find_ptr(interned_strings, (char *) text, leng);
  235. if (interned_str) {
  236. return zend_string_copy(interned_str);
  237. }
  238. interned_str = zend_string_init((char *) text, leng, 0);
  239. zend_hash_add_new_ptr(interned_strings, interned_str, interned_str);
  240. return interned_str;
  241. } else {
  242. return zend_string_init((char *) text, leng, 0);
  243. }
  244. }
  245. static void add_token(
  246. zval *return_value, int token_type, unsigned char *text, size_t leng, int lineno,
  247. zend_class_entry *token_class, HashTable *interned_strings) {
  248. zval token;
  249. if (token_class) {
  250. zend_object *obj = zend_objects_new(token_class);
  251. ZVAL_OBJ(&token, obj);
  252. ZVAL_LONG(OBJ_PROP_NUM(obj, 0), token_type);
  253. ZVAL_STR(OBJ_PROP_NUM(obj, 1), make_str(text, leng, interned_strings));
  254. ZVAL_LONG(OBJ_PROP_NUM(obj, 2), lineno);
  255. ZVAL_LONG(OBJ_PROP_NUM(obj, 3), text - LANG_SCNG(yy_start));
  256. /* If the class is extended with additional properties, initialized them as well. */
  257. if (UNEXPECTED(token_class->default_properties_count > 4)) {
  258. zval *dst = OBJ_PROP_NUM(obj, 4);
  259. zval *src = &token_class->default_properties_table[4];
  260. zval *end = token_class->default_properties_table
  261. + token_class->default_properties_count;
  262. for (; src < end; src++, dst++) {
  263. ZVAL_COPY_PROP(dst, src);
  264. }
  265. }
  266. } else if (token_type >= 256) {
  267. array_init_size(&token, 3);
  268. zend_hash_real_init_packed(Z_ARRVAL(token));
  269. ZEND_HASH_FILL_PACKED(Z_ARRVAL(token)) {
  270. ZEND_HASH_FILL_SET_LONG(token_type);
  271. ZEND_HASH_FILL_NEXT();
  272. ZEND_HASH_FILL_SET_STR(make_str(text, leng, interned_strings));
  273. ZEND_HASH_FILL_NEXT();
  274. ZEND_HASH_FILL_SET_LONG(lineno);
  275. ZEND_HASH_FILL_NEXT();
  276. } ZEND_HASH_FILL_END();
  277. } else {
  278. ZVAL_STR(&token, make_str(text, leng, interned_strings));
  279. }
  280. zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &token);
  281. }
  282. static bool tokenize(zval *return_value, zend_string *source, zend_class_entry *token_class)
  283. {
  284. zval source_zval;
  285. zend_lex_state original_lex_state;
  286. zval token;
  287. int token_type;
  288. int token_line = 1;
  289. int need_tokens = -1; /* for __halt_compiler lexing. -1 = disabled */
  290. HashTable interned_strings;
  291. ZVAL_STR_COPY(&source_zval, source);
  292. zend_save_lexical_state(&original_lex_state);
  293. zend_prepare_string_for_scanning(&source_zval, ZSTR_EMPTY_ALLOC());
  294. LANG_SCNG(yy_state) = yycINITIAL;
  295. zend_hash_init(&interned_strings, 0, NULL, NULL, 0);
  296. array_init(return_value);
  297. while ((token_type = lex_scan(&token, NULL))) {
  298. ZEND_ASSERT(token_type != T_ERROR);
  299. add_token(
  300. return_value, token_type, zendtext, zendleng, token_line,
  301. token_class, &interned_strings);
  302. if (Z_TYPE(token) != IS_UNDEF) {
  303. zval_ptr_dtor_nogc(&token);
  304. ZVAL_UNDEF(&token);
  305. }
  306. /* after T_HALT_COMPILER collect the next three non-dropped tokens */
  307. if (need_tokens != -1) {
  308. if (token_type != T_WHITESPACE && token_type != T_OPEN_TAG
  309. && token_type != T_COMMENT && token_type != T_DOC_COMMENT
  310. && --need_tokens == 0
  311. ) {
  312. /* fetch the rest into a T_INLINE_HTML */
  313. if (zendcursor < zendlimit) {
  314. add_token(
  315. return_value, T_INLINE_HTML, zendcursor, zendlimit - zendcursor,
  316. token_line, token_class, &interned_strings);
  317. }
  318. break;
  319. }
  320. } else if (token_type == T_HALT_COMPILER) {
  321. need_tokens = 3;
  322. }
  323. if (CG(increment_lineno)) {
  324. CG(zend_lineno)++;
  325. CG(increment_lineno) = 0;
  326. }
  327. token_line = CG(zend_lineno);
  328. }
  329. zval_ptr_dtor_str(&source_zval);
  330. zend_restore_lexical_state(&original_lex_state);
  331. zend_hash_destroy(&interned_strings);
  332. return 1;
  333. }
  334. struct event_context {
  335. zval *tokens;
  336. zend_class_entry *token_class;
  337. };
  338. static zval *extract_token_id_to_replace(zval *token_zv, const char *text, size_t length) {
  339. zval *id_zv, *text_zv;
  340. ZEND_ASSERT(token_zv);
  341. if (Z_TYPE_P(token_zv) == IS_ARRAY) {
  342. id_zv = zend_hash_index_find(Z_ARRVAL_P(token_zv), 0);
  343. text_zv = zend_hash_index_find(Z_ARRVAL_P(token_zv), 1);
  344. } else if (Z_TYPE_P(token_zv) == IS_OBJECT) {
  345. id_zv = OBJ_PROP_NUM(Z_OBJ_P(token_zv), 0);
  346. text_zv = OBJ_PROP_NUM(Z_OBJ_P(token_zv), 1);
  347. } else {
  348. return NULL;
  349. }
  350. /* There are multiple candidate tokens to which this feedback may apply,
  351. * check text to make sure this is the right one. */
  352. ZEND_ASSERT(Z_TYPE_P(text_zv) == IS_STRING);
  353. if (Z_STRLEN_P(text_zv) == length && !memcmp(Z_STRVAL_P(text_zv), text, length)) {
  354. return id_zv;
  355. }
  356. return NULL;
  357. }
  358. void on_event(
  359. zend_php_scanner_event event, int token, int line,
  360. const char *text, size_t length, void *context)
  361. {
  362. struct event_context *ctx = context;
  363. switch (event) {
  364. case ON_TOKEN:
  365. if (token == END) break;
  366. /* Special cases */
  367. if (token == ';' && LANG_SCNG(yy_leng) > 1) { /* ?> or ?>\n or ?>\r\n */
  368. token = T_CLOSE_TAG;
  369. } else if (token == T_ECHO && LANG_SCNG(yy_leng) == sizeof("<?=") - 1) {
  370. token = T_OPEN_TAG_WITH_ECHO;
  371. }
  372. add_token(
  373. ctx->tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL);
  374. break;
  375. case ON_FEEDBACK: {
  376. HashTable *tokens_ht = Z_ARRVAL_P(ctx->tokens);
  377. zval *token_zv, *id_zv = NULL;
  378. ZEND_HASH_REVERSE_FOREACH_VAL(tokens_ht, token_zv) {
  379. id_zv = extract_token_id_to_replace(token_zv, text, length);
  380. if (id_zv) {
  381. break;
  382. }
  383. } ZEND_HASH_FOREACH_END();
  384. ZEND_ASSERT(id_zv);
  385. ZVAL_LONG(id_zv, token);
  386. break;
  387. }
  388. case ON_STOP:
  389. if (LANG_SCNG(yy_cursor) != LANG_SCNG(yy_limit)) {
  390. add_token(ctx->tokens, T_INLINE_HTML, LANG_SCNG(yy_cursor),
  391. LANG_SCNG(yy_limit) - LANG_SCNG(yy_cursor), CG(zend_lineno),
  392. ctx->token_class, NULL);
  393. }
  394. break;
  395. }
  396. }
  397. static bool tokenize_parse(
  398. zval *return_value, zend_string *source, zend_class_entry *token_class)
  399. {
  400. zval source_zval;
  401. struct event_context ctx;
  402. zval token_stream;
  403. zend_lex_state original_lex_state;
  404. bool original_in_compilation;
  405. bool success;
  406. ZVAL_STR_COPY(&source_zval, source);
  407. original_in_compilation = CG(in_compilation);
  408. CG(in_compilation) = 1;
  409. zend_save_lexical_state(&original_lex_state);
  410. zend_prepare_string_for_scanning(&source_zval, ZSTR_EMPTY_ALLOC());
  411. array_init(&token_stream);
  412. ctx.tokens = &token_stream;
  413. ctx.token_class = token_class;
  414. CG(ast) = NULL;
  415. CG(ast_arena) = zend_arena_create(1024 * 32);
  416. LANG_SCNG(yy_state) = yycINITIAL;
  417. LANG_SCNG(on_event) = on_event;
  418. LANG_SCNG(on_event_context) = &ctx;
  419. if((success = (zendparse() == SUCCESS))) {
  420. ZVAL_COPY_VALUE(return_value, &token_stream);
  421. } else {
  422. zval_ptr_dtor(&token_stream);
  423. }
  424. zend_ast_destroy(CG(ast));
  425. zend_arena_destroy(CG(ast_arena));
  426. /* restore compiler and scanner global states */
  427. zend_restore_lexical_state(&original_lex_state);
  428. CG(in_compilation) = original_in_compilation;
  429. zval_ptr_dtor_str(&source_zval);
  430. return success;
  431. }
  432. static bool tokenize_common(
  433. zval *return_value, zend_string *source, zend_long flags, zend_class_entry *token_class)
  434. {
  435. if (flags & TOKEN_PARSE) {
  436. return tokenize_parse(return_value, source, token_class);
  437. } else {
  438. int success = tokenize(return_value, source, token_class);
  439. /* Normal token_get_all() should not throw. */
  440. zend_clear_exception();
  441. return success;
  442. }
  443. }
  444. /* }}} */
  445. /* {{{ */
  446. PHP_FUNCTION(token_get_all)
  447. {
  448. zend_string *source;
  449. zend_long flags = 0;
  450. ZEND_PARSE_PARAMETERS_START(1, 2)
  451. Z_PARAM_STR(source)
  452. Z_PARAM_OPTIONAL
  453. Z_PARAM_LONG(flags)
  454. ZEND_PARSE_PARAMETERS_END();
  455. if (!tokenize_common(return_value, source, flags, /* token_class */ NULL)) {
  456. RETURN_THROWS();
  457. }
  458. }
  459. /* }}} */
  460. /* {{{ */
  461. PHP_FUNCTION(token_name)
  462. {
  463. zend_long type;
  464. ZEND_PARSE_PARAMETERS_START(1, 1)
  465. Z_PARAM_LONG(type)
  466. ZEND_PARSE_PARAMETERS_END();
  467. const char *token_name = get_token_type_name(type);
  468. if (!token_name) {
  469. token_name = "UNKNOWN";
  470. }
  471. RETURN_STRING(token_name);
  472. }
  473. /* }}} */