|
| 1 | +#include "sapi/embed/php_embed.h" |
| 2 | +#include "Zend/zend_exceptions.h" |
| 3 | +#include "Zend/zend_interfaces.h" |
| 4 | +#include "Zend/zend_compile.h" |
| 5 | +#include <emscripten.h> |
| 6 | +#include <stdlib.h> |
| 7 | +#include <string.h> |
| 8 | + |
| 9 | +// From Zend/zend_exceptions.c for php 7.3 |
| 10 | +#if PHP_MAJOR_VERSION >= 8 |
| 11 | +#define GET_PROPERTY_SILENT(object, id) \ |
| 12 | + zend_read_property_ex(i_get_exception_base(object), (Z_OBJ_P(object)), ZSTR_KNOWN(id), 1, &rv) |
| 13 | +#else |
| 14 | +#define GET_PROPERTY_SILENT(object, id) \ |
| 15 | + zend_read_property_ex(i_get_exception_base(object), (object), ZSTR_KNOWN(id), 1, &rv) |
| 16 | +#endif |
| 17 | + |
| 18 | +// Source: php-src/sapi/php_cli.c |
| 19 | +static inline zend_class_entry *i_get_exception_base(zval *object) /* {{{ */ |
| 20 | +{ |
| 21 | + return instanceof_function(Z_OBJCE_P(object), zend_ce_exception) ? zend_ce_exception : zend_ce_error; |
| 22 | +} |
| 23 | +static void pib_cli_register_file_handles(void) /* {{{ */ |
| 24 | +{ |
| 25 | + php_stream /* *s_in, */ *s_out, *s_err; |
| 26 | + php_stream_context /* *sc_in=NULL, */ *sc_out=NULL, *sc_err=NULL; |
| 27 | + zend_constant /* ic, */ oc, ec; |
| 28 | + |
| 29 | + // s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in); |
| 30 | + s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out); |
| 31 | + s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err); |
| 32 | + |
| 33 | + if (/* s_in == NULL || */ s_out==NULL || s_err==NULL) { |
| 34 | + // if (s_in) php_stream_close(s_in); |
| 35 | + if (s_out) php_stream_close(s_out); |
| 36 | + if (s_err) php_stream_close(s_err); |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + // TODO: Support s_in |
| 41 | + // s_in_process = s_in; |
| 42 | + |
| 43 | + // TODO: Set up an empty stream instead for STDIN |
| 44 | + // php_stream_to_zval(s_in, &ic.value); |
| 45 | + php_stream_to_zval(s_out, &oc.value); |
| 46 | + php_stream_to_zval(s_err, &ec.value); |
| 47 | + |
| 48 | + /* |
| 49 | + ZEND_CONSTANT_SET_FLAGS(&ic, CONST_CS, 0); |
| 50 | + ic.name = zend_string_init_interned("STDIN", sizeof("STDIN")-1, 0); |
| 51 | + zend_register_constant(&ic); |
| 52 | + */ |
| 53 | + |
| 54 | + ZEND_CONSTANT_SET_FLAGS(&oc, CONST_CS, 0); |
| 55 | + oc.name = zend_string_init_interned("STDOUT", sizeof("STDOUT")-1, 0); |
| 56 | + zend_register_constant(&oc); |
| 57 | + |
| 58 | + ZEND_CONSTANT_SET_FLAGS(&ec, CONST_CS, 0); |
| 59 | + ec.name = zend_string_init_interned("STDERR", sizeof("STDERR")-1, 0); |
| 60 | + zend_register_constant(&ec); |
| 61 | +} |
| 62 | +/* }}} */ |
| 63 | + |
| 64 | +// Based on void zend_exception_error |
| 65 | +static void pib_report_exception(zend_object *ex) { |
| 66 | + // printf("exception=%llx\n", (long long)ex); |
| 67 | + zval exception; |
| 68 | + |
| 69 | + ZVAL_OBJ(&exception, ex); |
| 70 | + zend_class_entry *ce_exception = Z_OBJCE(exception); |
| 71 | + |
| 72 | + // Cast to string and report it. |
| 73 | + // zend_exception_error(ex, E_ERROR); |
| 74 | + if (ce_exception) { |
| 75 | + zval rv; |
| 76 | + zend_string *message = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_MESSAGE)); |
| 77 | + fprintf(stderr, "Uncaught throwable '%s': %s\n", ZSTR_VAL(ce_exception->name), ZSTR_VAL(message)); |
| 78 | + zend_string_release(message); |
| 79 | + zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_FILE)); |
| 80 | + zend_long line = zval_get_long(GET_PROPERTY_SILENT(&exception, ZEND_STR_LINE)); |
| 81 | + fprintf(stderr, "At %s:%d\n", ZSTR_VAL(file), line); |
| 82 | + zend_string_release(file); |
| 83 | + /* |
| 84 | + // Can't get this to work at the end of execution. |
| 85 | + if (instanceof_function(ce_exception, zend_ce_throwable)) { |
| 86 | + zval tmp; |
| 87 | + // TODO handle uncaught exception caused by __toString() |
| 88 | + zend_call_method_with_0_params(&exception, ce_exception, &ce_exception->__tostring, "__tostring", &tmp); |
| 89 | + if (Z_TYPE(tmp) == IS_STRING) { |
| 90 | + fprintf(stderr, "%s", Z_STRVAL(tmp)); |
| 91 | + } else { |
| 92 | + fprintf(stderr, "Calling __toString failed\n"); |
| 93 | + } |
| 94 | + zval_ptr_dtor(&tmp); |
| 95 | + } |
| 96 | + */ |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +// Based on code by https://github.com/oraoto/pib with modifications. |
| 101 | +int EMSCRIPTEN_KEEPALIVE pib_eval(char *code) { |
| 102 | + int ret = 0; |
| 103 | + // USE_ZEND_ALLOC prevents using fast shutdown. |
| 104 | + // putenv("USE_ZEND_ALLOC=0"); |
| 105 | + php_embed_init(0, NULL); |
| 106 | + pib_cli_register_file_handles(); |
| 107 | + |
| 108 | + // Show fatal E_COMPILE_ERRORs and other errors properly (startup errors are normally hidden) |
| 109 | + PG(display_startup_errors)=1; |
| 110 | + PG(during_request_startup)=0; |
| 111 | + |
| 112 | + // Enable error display to stdout |
| 113 | + PG(display_errors) = 1; |
| 114 | + |
| 115 | + zend_first_try { |
| 116 | + // Set error_reporting to E_ALL |
| 117 | + EG(error_reporting) = E_ALL; |
| 118 | + |
| 119 | + // Compile and execute the code |
| 120 | + // Use ZEND_COMPILE_POSITION_AT_OPEN_TAG to allow <?php tags |
| 121 | + zend_string *code_str = zend_string_init(code, strlen(code), 0); |
| 122 | + zend_op_array *op_array = zend_compile_string(code_str, "PIB", ZEND_COMPILE_POSITION_AT_OPEN_TAG); |
| 123 | + zend_string_release(code_str); |
| 124 | + |
| 125 | + if (op_array) { |
| 126 | + zval result; |
| 127 | + ZVAL_UNDEF(&result); |
| 128 | + zend_execute(op_array, &result); |
| 129 | + zval_ptr_dtor(&result); |
| 130 | + destroy_op_array(op_array); |
| 131 | + efree(op_array); |
| 132 | + ret = SUCCESS; |
| 133 | + } else { |
| 134 | + ret = FAILURE; |
| 135 | + } |
| 136 | + |
| 137 | + // If there was an uncaught error/exception, then report it. |
| 138 | + zend_object *ex = EG(exception); |
| 139 | + if (ex != NULL) { |
| 140 | + pib_report_exception(ex); |
| 141 | + } |
| 142 | + } zend_catch { |
| 143 | + zend_object *ex = EG(exception); |
| 144 | + if (ex != NULL) { |
| 145 | + EG(exception) = NULL; |
| 146 | + pib_report_exception(ex); |
| 147 | + EG(exception) = ex; |
| 148 | + } |
| 149 | + ret = EG(exit_status); |
| 150 | + } zend_end_try(); |
| 151 | + php_embed_shutdown(); |
| 152 | + fflush(stdout); |
| 153 | + fflush(stderr); |
| 154 | + return ret; |
| 155 | +} |
| 156 | + |
| 157 | +int EMSCRIPTEN_KEEPALIVE pib_force_exit() { |
| 158 | + emscripten_force_exit(0); |
| 159 | + return 0; |
| 160 | +} |
0 commit comments