123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577 |
- /*
- +----------------------------------------------------------------------+
- | Zend Engine, SCCP - Sparse Conditional Constant Propagation |
- +----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 of the PHP license, |
- | that is bundled with this package in the file LICENSE, and is |
- | available through the world-wide-web at the following url: |
- | https://www.php.net/license/3_01.txt |
- | If you did not receive a copy of the PHP license and are unable to |
- | obtain it through the world-wide-web, please send a note to |
- | license@php.net so we can mail you a copy immediately. |
- +----------------------------------------------------------------------+
- | Authors: Nikita Popov <nikic@php.net> |
- | Dmitry Stogov <dmitry@php.net> |
- +----------------------------------------------------------------------+
- */
- #include "php.h"
- #include "zend_API.h"
- #include "zend_exceptions.h"
- #include "zend_ini.h"
- #include "zend_type_info.h"
- #include "Optimizer/zend_optimizer_internal.h"
- #include "Optimizer/zend_call_graph.h"
- #include "Optimizer/zend_inference.h"
- #include "Optimizer/scdf.h"
- #include "Optimizer/zend_dump.h"
- /* This implements sparse conditional constant propagation (SCCP) based on the SCDF framework. The
- * used value lattice is defined as follows:
- *
- * BOT < {constant values} < TOP
- *
- * TOP indicates an underdefined value, i.e. that we do not yet know the value of variable.
- * BOT indicates an overdefined value, i.e. that we know the variable to be non-constant.
- *
- * All variables are optimistically initialized to TOP, apart from the implicit variables defined
- * at the start of the first block. Note that variables that MAY_BE_REF are *not* initialized to
- * BOT. We rely on the fact that any operation resulting in a reference will produce a BOT anyway.
- * This is better because such operations might never be reached due to the conditional nature of
- * the algorithm.
- *
- * The meet operation for phi functions is defined as follows:
- * BOT + any = BOT
- * TOP + any = any
- * C_i + C_i = C_i (i.e. two equal constants)
- * C_i + C_j = BOT (i.e. two different constants)
- *
- * When evaluating instructions TOP and BOT are handled as follows:
- * a) If any operand is BOT, the result is BOT. The main exception to this is op1 of ASSIGN, which
- * is ignored. However, if the op1 MAY_BE_REF we do have to propagate the BOT.
- * b) Otherwise, if the instruction can never be evaluated (either in general, or with the
- * specific modifiers) the result is BOT.
- * c) Otherwise, if any operand is TOP, the result is TOP.
- * d) Otherwise (at this point all operands are known and constant), if we can compute the result
- * for these specific constants (without throwing notices or similar) then that is the result.
- * e) Otherwise the result is BOT.
- *
- * It is sometimes possible to determine a result even if one argument is TOP / BOT, e.g. for things
- * like BOT*0. Right now we don't bother with this -- the only thing that is done is evaluating
- * TYPE_CHECKS based on the type information.
- *
- * Feasible successors for conditional branches are determined as follows:
- * a) If we don't support the branch type or branch on BOT, all successors are feasible.
- * b) Otherwise, if we branch on TOP none of the successors are feasible.
- * c) Otherwise (we branch on a constant), the feasible successors are marked based on the constant
- * (usually only one successor will be feasible).
- *
- * The original SCCP algorithm is extended with ability to propagate constant array
- * elements and object properties. The extension is based on a variation of Array
- * SSA form and its application to Spare Constant Propagation, described at
- * "Array SSA Form" by Vivek Sarkar, Kathleen Knobe and Stephen Fink in chapter
- * 16 of the SSA book.
- */
- #define SCP_DEBUG 0
- typedef struct _sccp_ctx {
- scdf_ctx scdf;
- zend_call_info **call_map;
- zval *values;
- zval top;
- zval bot;
- } sccp_ctx;
- #define TOP ((zend_uchar)-1)
- #define BOT ((zend_uchar)-2)
- #define PARTIAL_ARRAY ((zend_uchar)-3)
- #define PARTIAL_OBJECT ((zend_uchar)-4)
- #define IS_TOP(zv) (Z_TYPE_P(zv) == TOP)
- #define IS_BOT(zv) (Z_TYPE_P(zv) == BOT)
- #define IS_PARTIAL_ARRAY(zv) (Z_TYPE_P(zv) == PARTIAL_ARRAY)
- #define IS_PARTIAL_OBJECT(zv) (Z_TYPE_P(zv) == PARTIAL_OBJECT)
- #define MAKE_PARTIAL_ARRAY(zv) (Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
- #define MAKE_PARTIAL_OBJECT(zv) (Z_TYPE_INFO_P(zv) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
- #define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP)
- #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT)
- static void scp_dump_value(zval *zv) {
- if (IS_TOP(zv)) {
- fprintf(stderr, " top");
- } else if (IS_BOT(zv)) {
- fprintf(stderr, " bot");
- } else if (Z_TYPE_P(zv) == IS_ARRAY || IS_PARTIAL_ARRAY(zv)) {
- fprintf(stderr, " %s[", IS_PARTIAL_ARRAY(zv) ? "partial " : "");
- zend_dump_ht(Z_ARRVAL_P(zv));
- fprintf(stderr, "]");
- } else if (IS_PARTIAL_OBJECT(zv)) {
- fprintf(stderr, " {");
- zend_dump_ht(Z_ARRVAL_P(zv));
- fprintf(stderr, "}");
- } else {
- zend_dump_const(zv);
- }
- }
- static void empty_partial_array(zval *zv)
- {
- MAKE_PARTIAL_ARRAY(zv);
- Z_ARR_P(zv) = zend_new_array(8);
- }
- static void dup_partial_array(zval *dst, zval *src)
- {
- MAKE_PARTIAL_ARRAY(dst);
- Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src));
- }
- static void empty_partial_object(zval *zv)
- {
- MAKE_PARTIAL_OBJECT(zv);
- Z_ARR_P(zv) = zend_new_array(8);
- }
- static void dup_partial_object(zval *dst, zval *src)
- {
- MAKE_PARTIAL_OBJECT(dst);
- Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src));
- }
- static inline bool value_known(zval *zv) {
- return !IS_TOP(zv) && !IS_BOT(zv);
- }
- /* Sets new value for variable and ensures that it is lower or equal
- * the previous one in the constant propagation lattice. */
- static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) {
- zval *value = &ctx->values[var];
- if (IS_BOT(value) || IS_TOP(new)) {
- return;
- }
- #if SCP_DEBUG
- fprintf(stderr, "Lowering #%d.", var);
- zend_dump_var(scdf->op_array, IS_CV, scdf->ssa->vars[var].var);
- fprintf(stderr, " from");
- scp_dump_value(value);
- fprintf(stderr, " to");
- scp_dump_value(new);
- fprintf(stderr, "\n");
- #endif
- if (IS_TOP(value) || IS_BOT(new)) {
- zval_ptr_dtor_nogc(value);
- ZVAL_COPY(value, new);
- scdf_add_to_worklist(scdf, var);
- return;
- }
- /* Always replace PARTIAL_(ARRAY|OBJECT), as new maybe changed by join_partial_(arrays|object) */
- if (IS_PARTIAL_ARRAY(new) || IS_PARTIAL_OBJECT(new)) {
- if (Z_TYPE_P(value) != Z_TYPE_P(new)
- || zend_hash_num_elements(Z_ARR_P(new)) != zend_hash_num_elements(Z_ARR_P(value))) {
- zval_ptr_dtor_nogc(value);
- ZVAL_COPY(value, new);
- scdf_add_to_worklist(scdf, var);
- }
- return;
- }
- #if ZEND_DEBUG
- ZEND_ASSERT(zend_is_identical(value, new) ||
- (Z_TYPE_P(value) == IS_DOUBLE && Z_TYPE_P(new) == IS_DOUBLE && isnan(Z_DVAL_P(value)) && isnan(Z_DVAL_P(new))));
- #endif
- }
- static zval *get_op1_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
- if (opline->op1_type == IS_CONST) {
- return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op1.constant);
- } else if (ssa_op->op1_use != -1) {
- return &ctx->values[ssa_op->op1_use];
- } else {
- return NULL;
- }
- }
- static zval *get_op2_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
- if (opline->op2_type == IS_CONST) {
- return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op2.constant);
- } else if (ssa_op->op2_use != -1) {
- return &ctx->values[ssa_op->op2_use];
- } else {
- return NULL;
- }
- }
- static bool can_replace_op1(
- const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) {
- switch (opline->opcode) {
- case ZEND_PRE_INC:
- case ZEND_PRE_DEC:
- case ZEND_PRE_INC_OBJ:
- case ZEND_PRE_DEC_OBJ:
- case ZEND_POST_INC:
- case ZEND_POST_DEC:
- case ZEND_POST_INC_OBJ:
- case ZEND_POST_DEC_OBJ:
- case ZEND_ASSIGN:
- case ZEND_ASSIGN_REF:
- case ZEND_ASSIGN_DIM:
- case ZEND_ASSIGN_OBJ:
- case ZEND_ASSIGN_OBJ_REF:
- case ZEND_ASSIGN_OP:
- case ZEND_ASSIGN_DIM_OP:
- case ZEND_ASSIGN_OBJ_OP:
- case ZEND_ASSIGN_STATIC_PROP_OP:
- case ZEND_FETCH_DIM_W:
- case ZEND_FETCH_DIM_RW:
- case ZEND_FETCH_DIM_UNSET:
- case ZEND_FETCH_DIM_FUNC_ARG:
- case ZEND_FETCH_OBJ_W:
- case ZEND_FETCH_OBJ_RW:
- case ZEND_FETCH_OBJ_UNSET:
- case ZEND_FETCH_OBJ_FUNC_ARG:
- case ZEND_FETCH_LIST_W:
- case ZEND_UNSET_DIM:
- case ZEND_UNSET_OBJ:
- case ZEND_SEND_REF:
- case ZEND_SEND_VAR_EX:
- case ZEND_SEND_FUNC_ARG:
- case ZEND_SEND_UNPACK:
- case ZEND_SEND_ARRAY:
- case ZEND_SEND_USER:
- case ZEND_FE_RESET_RW:
- return 0;
- /* Do not accept CONST */
- case ZEND_ROPE_ADD:
- case ZEND_ROPE_END:
- case ZEND_BIND_STATIC:
- case ZEND_BIND_GLOBAL:
- case ZEND_MAKE_REF:
- case ZEND_UNSET_CV:
- case ZEND_ISSET_ISEMPTY_CV:
- return 0;
- case ZEND_INIT_ARRAY:
- case ZEND_ADD_ARRAY_ELEMENT:
- return !(opline->extended_value & ZEND_ARRAY_ELEMENT_REF);
- case ZEND_YIELD:
- return !(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE);
- case ZEND_VERIFY_RETURN_TYPE:
- // TODO: This would require a non-local change ???
- return 0;
- case ZEND_OP_DATA:
- return (opline - 1)->opcode != ZEND_ASSIGN_OBJ_REF &&
- (opline - 1)->opcode != ZEND_ASSIGN_STATIC_PROP_REF;
- default:
- if (ssa_op->op1_def != -1) {
- ZEND_UNREACHABLE();
- return 0;
- }
- }
- return 1;
- }
- static bool can_replace_op2(
- const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) {
- switch (opline->opcode) {
- /* Do not accept CONST */
- case ZEND_DECLARE_CLASS_DELAYED:
- case ZEND_BIND_LEXICAL:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- return 0;
- }
- return 1;
- }
- static bool try_replace_op1(
- sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) {
- if (ssa_op->op1_use == var && can_replace_op1(ctx->scdf.op_array, opline, ssa_op)) {
- zval zv;
- ZVAL_COPY(&zv, value);
- if (zend_optimizer_update_op1_const(ctx->scdf.op_array, opline, &zv)) {
- return 1;
- } else {
- // TODO: check the following special cases ???
- switch (opline->opcode) {
- case ZEND_CASE:
- opline->opcode = ZEND_IS_EQUAL;
- goto replace_op1_simple;
- case ZEND_CASE_STRICT:
- opline->opcode = ZEND_IS_IDENTICAL;
- goto replace_op1_simple;
- case ZEND_FETCH_LIST_R:
- case ZEND_SWITCH_STRING:
- case ZEND_SWITCH_LONG:
- case ZEND_MATCH:
- replace_op1_simple:
- if (Z_TYPE(zv) == IS_STRING) {
- zend_string_hash_val(Z_STR(zv));
- }
- opline->op1.constant = zend_optimizer_add_literal(ctx->scdf.op_array, &zv);
- opline->op1_type = IS_CONST;
- return 1;
- case ZEND_INSTANCEOF:
- zval_ptr_dtor_nogc(&zv);
- ZVAL_FALSE(&zv);
- opline->opcode = ZEND_QM_ASSIGN;
- opline->op1_type = IS_CONST;
- opline->op1.constant = zend_optimizer_add_literal(ctx->scdf.op_array, &zv);
- opline->op2_type = IS_UNUSED;
- if (ssa_op->op2_use >= 0) {
- ZEND_ASSERT(ssa_op->op2_def == -1);
- zend_ssa_unlink_use_chain(ctx->scdf.ssa, ssa_op - ctx->scdf.ssa->ops, ssa_op->op2_use);
- ssa_op->op2_use = -1;
- ssa_op->op2_use_chain = -1;
- }
- return 1;
- default:
- break;
- }
- zval_ptr_dtor_nogc(&zv);
- }
- }
- return 0;
- }
- static bool try_replace_op2(
- sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) {
- if (ssa_op->op2_use == var && can_replace_op2(ctx->scdf.op_array, opline, ssa_op)) {
- zval zv;
- ZVAL_COPY(&zv, value);
- if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline, &zv)) {
- return 1;
- } else {
- switch (opline->opcode) {
- case ZEND_FETCH_CLASS:
- if (Z_TYPE(zv) == IS_STRING) {
- ZEND_ASSERT((opline + 1)->opcode == ZEND_INSTANCEOF);
- ZEND_ASSERT(ssa_op->result_def == (ssa_op + 1)->op2_use);
- if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline + 1, &zv)) {
- zend_ssa_op *next_op = ssa_op + 1;
- zend_ssa_unlink_use_chain(ctx->scdf.ssa, next_op - ctx->scdf.ssa->ops, next_op->op2_use);
- next_op->op2_use = -1;
- next_op->op2_use_chain = -1;
- zend_ssa_remove_result_def(ctx->scdf.ssa, ssa_op);
- MAKE_NOP(opline);
- return 1;
- }
- }
- default:
- break;
- }
- zval_ptr_dtor_nogc(&zv);
- }
- }
- return 0;
- }
- static inline int ct_eval_binary_op(zval *result, zend_uchar binop, zval *op1, zval *op2) {
- /* TODO: We could implement support for evaluation of + on partial arrays. */
- if (IS_PARTIAL_ARRAY(op1) || IS_PARTIAL_ARRAY(op2)) {
- return FAILURE;
- }
- return zend_optimizer_eval_binary_op(result, binop, op1, op2);
- }
- static inline int ct_eval_bool_cast(zval *result, zval *op) {
- if (IS_PARTIAL_ARRAY(op)) {
- if (zend_hash_num_elements(Z_ARRVAL_P(op)) == 0) {
- /* An empty partial array may be non-empty at runtime, we don't know whether the
- * result will be true or false. */
- return FAILURE;
- }
- ZVAL_TRUE(result);
- return SUCCESS;
- }
- ZVAL_BOOL(result, zend_is_true(op));
- return SUCCESS;
- }
- static inline int zval_to_string_offset(zend_long *result, zval *op) {
- switch (Z_TYPE_P(op)) {
- case IS_LONG:
- *result = Z_LVAL_P(op);
- return SUCCESS;
- case IS_STRING:
- if (IS_LONG == is_numeric_string(
- Z_STRVAL_P(op), Z_STRLEN_P(op), result, NULL, 0)) {
- return SUCCESS;
- }
- return FAILURE;
- default:
- return FAILURE;
- }
- }
- static inline int fetch_array_elem(zval **result, zval *op1, zval *op2) {
- switch (Z_TYPE_P(op2)) {
- case IS_NULL:
- *result = zend_hash_find(Z_ARR_P(op1), ZSTR_EMPTY_ALLOC());
- return SUCCESS;
- case IS_FALSE:
- *result = zend_hash_index_find(Z_ARR_P(op1), 0);
- return SUCCESS;
- case IS_TRUE:
- *result = zend_hash_index_find(Z_ARR_P(op1), 1);
- return SUCCESS;
- case IS_LONG:
- *result = zend_hash_index_find(Z_ARR_P(op1), Z_LVAL_P(op2));
- return SUCCESS;
- case IS_DOUBLE: {
- zend_long lval = zend_dval_to_lval(Z_DVAL_P(op2));
- if (!zend_is_long_compatible(Z_DVAL_P(op2), lval)) {
- return FAILURE;
- }
- *result = zend_hash_index_find(Z_ARR_P(op1), lval);
- return SUCCESS;
- }
- case IS_STRING:
- *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2));
- return SUCCESS;
- default:
- return FAILURE;
- }
- }
- static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int support_strings) {
- if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) {
- zval *value;
- if (fetch_array_elem(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) {
- ZVAL_COPY(result, value);
- return SUCCESS;
- }
- } else if (support_strings && Z_TYPE_P(op1) == IS_STRING) {
- zend_long index;
- if (zval_to_string_offset(&index, op2) == FAILURE) {
- return FAILURE;
- }
- if (index >= 0 && index < Z_STRLEN_P(op1)) {
- ZVAL_STR(result, zend_string_init(&Z_STRVAL_P(op1)[index], 1, 0));
- return SUCCESS;
- }
- }
- return FAILURE;
- }
- /* op1 may be NULL here to indicate an unset value */
- static inline int ct_eval_isset_isempty(zval *result, uint32_t extended_value, zval *op1) {
- zval zv;
- if (!(extended_value & ZEND_ISEMPTY)) {
- ZVAL_BOOL(result, op1 && Z_TYPE_P(op1) != IS_NULL);
- return SUCCESS;
- } else if (!op1) {
- ZVAL_TRUE(result);
- return SUCCESS;
- } else if (ct_eval_bool_cast(&zv, op1) == SUCCESS) {
- ZVAL_BOOL(result, Z_TYPE(zv) == IS_FALSE);
- return SUCCESS;
- } else {
- return FAILURE;
- }
- }
- static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
- if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) {
- zval *value;
- if (fetch_array_elem(&value, op1, op2) == FAILURE) {
- return FAILURE;
- }
- if (IS_PARTIAL_ARRAY(op1) && (!value || IS_BOT(value))) {
- return FAILURE;
- }
- return ct_eval_isset_isempty(result, extended_value, value);
- } else if (Z_TYPE_P(op1) == IS_STRING) {
- // TODO
- return FAILURE;
- } else {
- ZVAL_BOOL(result, (extended_value & ZEND_ISEMPTY));
- return SUCCESS;
- }
- }
- static inline int ct_eval_del_array_elem(zval *result, zval *key) {
- ZEND_ASSERT(IS_PARTIAL_ARRAY(result));
- switch (Z_TYPE_P(key)) {
- case IS_NULL:
- zend_hash_del(Z_ARR_P(result), ZSTR_EMPTY_ALLOC());
- break;
- case IS_FALSE:
- zend_hash_index_del(Z_ARR_P(result), 0);
- break;
- case IS_TRUE:
- zend_hash_index_del(Z_ARR_P(result), 1);
- break;
- case IS_LONG:
- zend_hash_index_del(Z_ARR_P(result), Z_LVAL_P(key));
- break;
- case IS_DOUBLE: {
- zend_long lval = zend_dval_to_lval(Z_DVAL_P(key));
- if (!zend_is_long_compatible(Z_DVAL_P(key), lval)) {
- return FAILURE;
- }
- zend_hash_index_del(Z_ARR_P(result), lval);
- break;
- }
- case IS_STRING:
- zend_symtable_del(Z_ARR_P(result), Z_STR_P(key));
- break;
- default:
- return FAILURE;
- }
- return SUCCESS;
- }
- static inline int ct_eval_add_array_elem(zval *result, zval *value, zval *key) {
- if (!key) {
- SEPARATE_ARRAY(result);
- if ((value = zend_hash_next_index_insert(Z_ARR_P(result), value))) {
- Z_TRY_ADDREF_P(value);
- return SUCCESS;
- }
- return FAILURE;
- }
- switch (Z_TYPE_P(key)) {
- case IS_NULL:
- SEPARATE_ARRAY(result);
- value = zend_hash_update(Z_ARR_P(result), ZSTR_EMPTY_ALLOC(), value);
- break;
- case IS_FALSE:
- SEPARATE_ARRAY(result);
- value = zend_hash_index_update(Z_ARR_P(result), 0, value);
- break;
- case IS_TRUE:
- SEPARATE_ARRAY(result);
- value = zend_hash_index_update(Z_ARR_P(result), 1, value);
- break;
- case IS_LONG:
- SEPARATE_ARRAY(result);
- value = zend_hash_index_update(Z_ARR_P(result), Z_LVAL_P(key), value);
- break;
- case IS_DOUBLE: {
- zend_long lval = zend_dval_to_lval(Z_DVAL_P(key));
- if (!zend_is_long_compatible(Z_DVAL_P(key), lval)) {
- return FAILURE;
- }
- SEPARATE_ARRAY(result);
- value = zend_hash_index_update(
- Z_ARR_P(result), lval, value);
- break;
- }
- case IS_STRING:
- SEPARATE_ARRAY(result);
- value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value);
- break;
- default:
- return FAILURE;
- }
- Z_TRY_ADDREF_P(value);
- return SUCCESS;
- }
- static inline int ct_eval_add_array_unpack(zval *result, zval *array) {
- zend_string *key;
- zval *value;
- if (Z_TYPE_P(array) != IS_ARRAY) {
- return FAILURE;
- }
- SEPARATE_ARRAY(result);
- ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(array), key, value) {
- if (key) {
- value = zend_hash_update(Z_ARR_P(result), key, value);
- } else {
- value = zend_hash_next_index_insert(Z_ARR_P(result), value);
- }
- if (!value) {
- return FAILURE;
- }
- Z_TRY_ADDREF_P(value);
- } ZEND_HASH_FOREACH_END();
- return SUCCESS;
- }
- static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) {
- switch (Z_TYPE_P(result)) {
- case IS_NULL:
- case IS_FALSE:
- array_init(result);
- ZEND_FALLTHROUGH;
- case IS_ARRAY:
- case PARTIAL_ARRAY:
- return ct_eval_add_array_elem(result, value, key);
- case IS_STRING:
- // TODO Before enabling this case, make sure ARRAY_DIM result op is correct
- #if 0
- zend_long index;
- zend_string *new_str, *value_str;
- if (!key || Z_TYPE_P(value) == IS_ARRAY
- || zval_to_string_offset(&index, key) == FAILURE || index < 0) {
- return FAILURE;
- }
- if (index >= Z_STRLEN_P(result)) {
- new_str = zend_string_alloc(index + 1, 0);
- memcpy(ZSTR_VAL(new_str), Z_STRVAL_P(result), Z_STRLEN_P(result));
- memset(ZSTR_VAL(new_str) + Z_STRLEN_P(result), ' ', index - Z_STRLEN_P(result));
- ZSTR_VAL(new_str)[index + 1] = 0;
- } else {
- new_str = zend_string_init(Z_STRVAL_P(result), Z_STRLEN_P(result), 0);
- }
- value_str = zval_get_string(value);
- ZVAL_STR(result, new_str);
- Z_STRVAL_P(result)[index] = ZSTR_VAL(value_str)[0];
- zend_string_release_ex(value_str, 0);
- #endif
- return FAILURE;
- default:
- return FAILURE;
- }
- }
- static inline int fetch_obj_prop(zval **result, zval *op1, zval *op2) {
- switch (Z_TYPE_P(op2)) {
- case IS_STRING:
- *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2));
- return SUCCESS;
- default:
- return FAILURE;
- }
- }
- static inline int ct_eval_fetch_obj(zval *result, zval *op1, zval *op2) {
- if (IS_PARTIAL_OBJECT(op1)) {
- zval *value;
- if (fetch_obj_prop(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) {
- ZVAL_COPY(result, value);
- return SUCCESS;
- }
- }
- return FAILURE;
- }
- static inline int ct_eval_isset_obj(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
- if (IS_PARTIAL_OBJECT(op1)) {
- zval *value;
- if (fetch_obj_prop(&value, op1, op2) == FAILURE) {
- return FAILURE;
- }
- if (!value || IS_BOT(value)) {
- return FAILURE;
- }
- return ct_eval_isset_isempty(result, extended_value, value);
- } else {
- ZVAL_BOOL(result, (extended_value & ZEND_ISEMPTY));
- return SUCCESS;
- }
- }
- static inline int ct_eval_del_obj_prop(zval *result, zval *key) {
- ZEND_ASSERT(IS_PARTIAL_OBJECT(result));
- switch (Z_TYPE_P(key)) {
- case IS_STRING:
- zend_symtable_del(Z_ARR_P(result), Z_STR_P(key));
- break;
- default:
- return FAILURE;
- }
- return SUCCESS;
- }
- static inline int ct_eval_add_obj_prop(zval *result, zval *value, zval *key) {
- switch (Z_TYPE_P(key)) {
- case IS_STRING:
- value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value);
- break;
- default:
- return FAILURE;
- }
- Z_TRY_ADDREF_P(value);
- return SUCCESS;
- }
- static inline int ct_eval_assign_obj(zval *result, zval *value, zval *key) {
- switch (Z_TYPE_P(result)) {
- case IS_NULL:
- case IS_FALSE:
- empty_partial_object(result);
- ZEND_FALLTHROUGH;
- case PARTIAL_OBJECT:
- return ct_eval_add_obj_prop(result, value, key);
- default:
- return FAILURE;
- }
- }
- static inline int ct_eval_incdec(zval *result, zend_uchar opcode, zval *op1) {
- if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) {
- return FAILURE;
- }
- ZVAL_COPY(result, op1);
- if (opcode == ZEND_PRE_INC
- || opcode == ZEND_POST_INC
- || opcode == ZEND_PRE_INC_OBJ
- || opcode == ZEND_POST_INC_OBJ) {
- increment_function(result);
- } else {
- decrement_function(result);
- }
- return SUCCESS;
- }
- static inline void ct_eval_type_check(zval *result, uint32_t type_mask, zval *op1) {
- uint32_t type = Z_TYPE_P(op1);
- if (type == PARTIAL_ARRAY) {
- type = IS_ARRAY;
- } else if (type == PARTIAL_OBJECT) {
- type = IS_OBJECT;
- }
- ZVAL_BOOL(result, (type_mask >> type) & 1);
- }
- static inline int ct_eval_in_array(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
- HashTable *ht;
- bool res;
- if (Z_TYPE_P(op2) != IS_ARRAY) {
- return FAILURE;
- }
- ht = Z_ARRVAL_P(op2);
- if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) {
- res = zend_hash_exists(ht, Z_STR_P(op1));
- } else if (extended_value) {
- if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) {
- res = zend_hash_index_exists(ht, Z_LVAL_P(op1));
- } else {
- res = 0;
- }
- } else if (Z_TYPE_P(op1) <= IS_FALSE) {
- res = zend_hash_exists(ht, ZSTR_EMPTY_ALLOC());
- } else {
- zend_string *key;
- zval key_tmp;
- res = 0;
- ZEND_HASH_FOREACH_STR_KEY(ht, key) {
- ZVAL_STR(&key_tmp, key);
- if (zend_compare(op1, &key_tmp) == 0) {
- res = 1;
- break;
- }
- } ZEND_HASH_FOREACH_END();
- }
- ZVAL_BOOL(result, res);
- return SUCCESS;
- }
- static inline int ct_eval_array_key_exists(zval *result, zval *op1, zval *op2) {
- zval *value;
- if (Z_TYPE_P(op2) != IS_ARRAY && !IS_PARTIAL_ARRAY(op2)) {
- return FAILURE;
- }
- if (Z_TYPE_P(op1) != IS_STRING && Z_TYPE_P(op1) != IS_LONG && Z_TYPE_P(op1) != IS_NULL) {
- return FAILURE;
- }
- if (fetch_array_elem(&value, op2, op1) == FAILURE) {
- return FAILURE;
- }
- if (IS_PARTIAL_ARRAY(op2) && (!value || IS_BOT(value))) {
- return FAILURE;
- }
- ZVAL_BOOL(result, value != NULL);
- return SUCCESS;
- }
- static bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **args) {
- /* Functions in this list must always produce the same result for the same arguments,
- * and have no dependence on global state (such as locales). It is okay if they throw
- * or warn on invalid arguments, as we detect this and will discard the evaluation result. */
- if (false
- || zend_string_equals_literal(name, "array_diff")
- || zend_string_equals_literal(name, "array_diff_assoc")
- || zend_string_equals_literal(name, "array_diff_key")
- || zend_string_equals_literal(name, "array_flip")
- || zend_string_equals_literal(name, "array_is_list")
- || zend_string_equals_literal(name, "array_key_exists")
- || zend_string_equals_literal(name, "array_keys")
- || zend_string_equals_literal(name, "array_merge")
- || zend_string_equals_literal(name, "array_merge_recursive")
- || zend_string_equals_literal(name, "array_replace")
- || zend_string_equals_literal(name, "array_replace_recursive")
- || zend_string_equals_literal(name, "array_unique")
- || zend_string_equals_literal(name, "array_values")
- || zend_string_equals_literal(name, "base64_decode")
- || zend_string_equals_literal(name, "base64_encode")
- #ifndef ZEND_WIN32
- /* On Windows this function may be code page dependent. */
- || zend_string_equals_literal(name, "dirname")
- #endif
- || zend_string_equals_literal(name, "explode")
- || zend_string_equals_literal(name, "imagetypes")
- || zend_string_equals_literal(name, "in_array")
- || zend_string_equals_literal(name, "implode")
- || zend_string_equals_literal(name, "ltrim")
- || zend_string_equals_literal(name, "php_sapi_name")
- || zend_string_equals_literal(name, "php_uname")
- || zend_string_equals_literal(name, "phpversion")
- || zend_string_equals_literal(name, "pow")
- || zend_string_equals_literal(name, "preg_quote")
- || zend_string_equals_literal(name, "rawurldecode")
- || zend_string_equals_literal(name, "rawurlencode")
- || zend_string_equals_literal(name, "rtrim")
- || zend_string_equals_literal(name, "serialize")
- || zend_string_equals_literal(name, "str_contains")
- || zend_string_equals_literal(name, "str_ends_with")
- || zend_string_equals_literal(name, "str_replace")
- || zend_string_equals_literal(name, "str_split")
- || zend_string_equals_literal(name, "str_starts_with")
- || zend_string_equals_literal(name, "strpos")
- || zend_string_equals_literal(name, "strstr")
- || zend_string_equals_literal(name, "substr")
- || zend_string_equals_literal(name, "trim")
- || zend_string_equals_literal(name, "urldecode")
- || zend_string_equals_literal(name, "urlencode")
- || zend_string_equals_literal(name, "version_compare")
- ) {
- return true;
- }
- if (num_args == 2) {
- if (zend_string_equals_literal(name, "str_repeat")) {
- /* Avoid creating overly large strings at compile-time. */
- bool overflow;
- return Z_TYPE_P(args[0]) == IS_STRING
- && Z_TYPE_P(args[1]) == IS_LONG
- && zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) < 64 * 1024
- && !overflow;
- }
- return false;
- }
- return false;
- }
- /* The functions chosen here are simple to implement and either likely to affect a branch,
- * or just happened to be commonly used with constant operands in WP (need to test other
- * applications as well, of course). */
- static inline int ct_eval_func_call(
- zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
- uint32_t i;
- zend_function *func = zend_hash_find_ptr(CG(function_table), name);
- if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
- return FAILURE;
- }
- if (num_args == 1) {
- /* Handle a few functions for which we manually implement evaluation here. */
- if (zend_string_equals_literal(name, "chr")) {
- zend_long c;
- if (Z_TYPE_P(args[0]) != IS_LONG) {
- return FAILURE;
- }
- c = Z_LVAL_P(args[0]) & 0xff;
- ZVAL_CHAR(result, c);
- return SUCCESS;
- } else if (zend_string_equals_literal(name, "count")) {
- if (Z_TYPE_P(args[0]) != IS_ARRAY) {
- return FAILURE;
- }
- ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0])));
- return SUCCESS;
- } else if (zend_string_equals_literal(name, "ini_get")) {
- zend_ini_entry *ini_entry;
- if (Z_TYPE_P(args[0]) != IS_STRING) {
- return FAILURE;
- }
- ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0]));
- if (!ini_entry) {
- if (PG(enable_dl)) {
- return FAILURE;
- }
- ZVAL_FALSE(result);
- } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) {
- return FAILURE;
- } else if (ini_entry->value) {
- ZVAL_STR_COPY(result, ini_entry->value);
- } else {
- ZVAL_EMPTY_STRING(result);
- }
- return SUCCESS;
- }
- }
- if (!can_ct_eval_func_call(name, num_args, args)) {
- return FAILURE;
- }
- zend_execute_data *prev_execute_data = EG(current_execute_data);
- zend_execute_data *execute_data, dummy_frame;
- zend_op dummy_opline;
- /* Add a dummy frame to get the correct strict_types behavior. */
- memset(&dummy_frame, 0, sizeof(zend_execute_data));
- memset(&dummy_opline, 0, sizeof(zend_op));
- dummy_frame.func = (zend_function *) op_array;
- dummy_frame.opline = &dummy_opline;
- dummy_opline.opcode = ZEND_DO_FCALL;
- execute_data = safe_emalloc(num_args, sizeof(zval), ZEND_CALL_FRAME_SLOT * sizeof(zval));
- memset(execute_data, 0, sizeof(zend_execute_data));
- execute_data->prev_execute_data = &dummy_frame;
- EG(current_execute_data) = execute_data;
- /* Enable suppression and counting of warnings. */
- ZEND_ASSERT(EG(capture_warnings_during_sccp) == 0);
- EG(capture_warnings_during_sccp) = 1;
- EX(func) = func;
- EX_NUM_ARGS() = num_args;
- for (i = 0; i < num_args; i++) {
- ZVAL_COPY(EX_VAR_NUM(i), args[i]);
- }
- ZVAL_NULL(result);
- func->internal_function.handler(execute_data, result);
- for (i = 0; i < num_args; i++) {
- zval_ptr_dtor_nogc(EX_VAR_NUM(i));
- }
- int retval = SUCCESS;
- if (EG(exception)) {
- zval_ptr_dtor(result);
- zend_clear_exception();
- retval = FAILURE;
- }
- if (EG(capture_warnings_during_sccp) > 1) {
- zval_ptr_dtor(result);
- retval = FAILURE;
- }
- EG(capture_warnings_during_sccp) = 0;
- efree(execute_data);
- EG(current_execute_data) = prev_execute_data;
- return retval;
- }
- #define SET_RESULT(op, zv) do { \
- if (ssa_op->op##_def >= 0) { \
- set_value(scdf, ctx, ssa_op->op##_def, zv); \
- } \
- } while (0)
- #define SET_RESULT_BOT(op) SET_RESULT(op, &ctx->bot)
- #define SET_RESULT_TOP(op) SET_RESULT(op, &ctx->top)
- #define SKIP_IF_TOP(op) if (IS_TOP(op)) return;
- static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op) {
- sccp_ctx *ctx = (sccp_ctx *) scdf;
- zval *op1, *op2, zv; /* zv is a temporary to hold result values */
- op1 = get_op1_value(ctx, opline, ssa_op);
- op2 = get_op2_value(ctx, opline, ssa_op);
- switch (opline->opcode) {
- case ZEND_ASSIGN:
- /* The value of op1 is irrelevant here, because we are overwriting it
- * -- unless it can be a reference, in which case we propagate a BOT.
- * The result is also BOT in this case, because it might be a typed reference. */
- if (IS_BOT(op1) && (ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF)) {
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(result);
- } else {
- SET_RESULT(op1, op2);
- SET_RESULT(result, op2);
- }
- return;
- case ZEND_TYPE_CHECK:
- /* We may be able to evaluate TYPE_CHECK based on type inference info,
- * even if we don't know the precise value. */
- if (!value_known(op1)) {
- uint32_t type = ctx->scdf.ssa->var_info[ssa_op->op1_use].type;
- uint32_t expected_type_mask = opline->extended_value;
- if (!(type & expected_type_mask) && !(type & MAY_BE_UNDEF)) {
- ZVAL_FALSE(&zv);
- SET_RESULT(result, &zv);
- return;
- } else if (!(type & ((MAY_BE_ANY|MAY_BE_UNDEF) - expected_type_mask))
- && !(expected_type_mask & MAY_BE_RESOURCE)) {
- ZVAL_TRUE(&zv);
- SET_RESULT(result, &zv);
- return;
- }
- }
- break;
- case ZEND_ASSIGN_DIM:
- {
- zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
- /* If $a in $a[$b]=$c is UNDEF, treat it like NULL. There is no warning. */
- if ((ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_ANY) == 0) {
- op1 = &EG(uninitialized_zval);
- }
- if (IS_BOT(op1)) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- return;
- }
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(data);
- if (op2) {
- SKIP_IF_TOP(op2);
- }
- if (op2 && IS_BOT(op2)) {
- /* Update of unknown index */
- SET_RESULT_BOT(result);
- if (ssa_op->op1_def >= 0) {
- empty_partial_array(&zv);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&zv);
- } else {
- SET_RESULT_BOT(op1);
- }
- return;
- }
- if (IS_BOT(data)) {
- SET_RESULT_BOT(result);
- if ((IS_PARTIAL_ARRAY(op1)
- || Z_TYPE_P(op1) == IS_NULL
- || Z_TYPE_P(op1) == IS_FALSE
- || Z_TYPE_P(op1) == IS_ARRAY)
- && ssa_op->op1_def >= 0) {
- if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) {
- empty_partial_array(&zv);
- } else {
- dup_partial_array(&zv, op1);
- }
- if (!op2) {
- /* We can't add NEXT element into partial array (skip it) */
- SET_RESULT(op1, &zv);
- } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) {
- SET_RESULT(op1, &zv);
- } else {
- SET_RESULT_BOT(op1);
- }
- zval_ptr_dtor_nogc(&zv);
- } else {
- SET_RESULT_BOT(op1);
- }
- } else {
- if (IS_PARTIAL_ARRAY(op1)) {
- dup_partial_array(&zv, op1);
- } else {
- ZVAL_COPY(&zv, op1);
- }
- if (!op2 && IS_PARTIAL_ARRAY(&zv)) {
- /* We can't add NEXT element into partial array (skip it) */
- SET_RESULT(result, data);
- SET_RESULT(op1, &zv);
- } else if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) {
- /* Mark array containing partial array as partial */
- if (IS_PARTIAL_ARRAY(data)) {
- MAKE_PARTIAL_ARRAY(&zv);
- }
- SET_RESULT(result, data);
- SET_RESULT(op1, &zv);
- } else {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- }
- zval_ptr_dtor_nogc(&zv);
- }
- return;
- }
- case ZEND_ASSIGN_OBJ:
- if (ssa_op->op1_def >= 0
- && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
- zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
- zend_ssa_var_info *var_info = &ctx->scdf.ssa->var_info[ssa_op->op1_use];
- /* Don't try to propagate assignments to (potentially) typed properties. We would
- * need to deal with errors and type conversions first. */
- if (!var_info->ce || (var_info->ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- return;
- }
- if (IS_BOT(op1)) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- return;
- }
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(data);
- SKIP_IF_TOP(op2);
- if (IS_BOT(op2)) {
- /* Update of unknown property */
- SET_RESULT_BOT(result);
- empty_partial_object(&zv);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&zv);
- return;
- }
- if (IS_BOT(data)) {
- SET_RESULT_BOT(result);
- if (IS_PARTIAL_OBJECT(op1)
- || Z_TYPE_P(op1) == IS_NULL
- || Z_TYPE_P(op1) == IS_FALSE) {
- if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) {
- empty_partial_object(&zv);
- } else {
- dup_partial_object(&zv, op1);
- }
- if (ct_eval_del_obj_prop(&zv, op2) == SUCCESS) {
- SET_RESULT(op1, &zv);
- } else {
- SET_RESULT_BOT(op1);
- }
- zval_ptr_dtor_nogc(&zv);
- } else {
- SET_RESULT_BOT(op1);
- }
- } else {
- if (IS_PARTIAL_OBJECT(op1)) {
- dup_partial_object(&zv, op1);
- } else {
- ZVAL_COPY(&zv, op1);
- }
- if (ct_eval_assign_obj(&zv, data, op2) == SUCCESS) {
- SET_RESULT(result, data);
- SET_RESULT(op1, &zv);
- } else {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- }
- zval_ptr_dtor_nogc(&zv);
- }
- } else {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- }
- return;
- case ZEND_SEND_VAL:
- case ZEND_SEND_VAR:
- {
- /* If the value of a SEND for an ICALL changes, we need to reconsider the
- * ICALL result value. Otherwise we can ignore the opcode. */
- zend_call_info *call;
- if (!ctx->call_map) {
- return;
- }
- call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
- if (IS_TOP(op1) || !call || !call->caller_call_opline
- || call->caller_call_opline->opcode != ZEND_DO_ICALL) {
- return;
- }
- opline = call->caller_call_opline;
- ssa_op = &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes];
- break;
- }
- case ZEND_INIT_ARRAY:
- case ZEND_ADD_ARRAY_ELEMENT:
- {
- zval *result = NULL;
- if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) {
- result = &ctx->values[ssa_op->result_use];
- if (IS_BOT(result)) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- return;
- }
- SKIP_IF_TOP(result);
- }
- if (op1) {
- SKIP_IF_TOP(op1);
- }
- if (op2) {
- SKIP_IF_TOP(op2);
- }
- /* We want to avoid keeping around intermediate arrays for each SSA variable in the
- * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode
- * and use a NULL value everywhere else. */
- if (result && Z_TYPE_P(result) == IS_NULL) {
- SET_RESULT_BOT(result);
- return;
- }
- if (op2 && IS_BOT(op2)) {
- /* Update of unknown index */
- SET_RESULT_BOT(op1);
- if (ssa_op->result_def >= 0) {
- empty_partial_array(&zv);
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- } else {
- SET_RESULT_BOT(result);
- }
- return;
- }
- if ((op1 && IS_BOT(op1))
- || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
- SET_RESULT_BOT(op1);
- if (ssa_op->result_def >= 0) {
- if (!result) {
- empty_partial_array(&zv);
- } else {
- MAKE_PARTIAL_ARRAY(result);
- ZVAL_COPY_VALUE(&zv, result);
- ZVAL_NULL(result);
- }
- if (!op2) {
- /* We can't add NEXT element into partial array (skip it) */
- SET_RESULT(result, &zv);
- } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- } else {
- SET_RESULT_BOT(result);
- }
- zval_ptr_dtor_nogc(&zv);
- } else {
- /* If any operand is BOT, mark the result as BOT right away.
- * Exceptions to this rule are handled above. */
- SET_RESULT_BOT(result);
- }
- } else {
- if (result) {
- ZVAL_COPY_VALUE(&zv, result);
- ZVAL_NULL(result);
- } else {
- array_init(&zv);
- }
- if (op1) {
- if (!op2 && IS_PARTIAL_ARRAY(&zv)) {
- /* We can't add NEXT element into partial array (skip it) */
- SET_RESULT(result, &zv);
- } else if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) {
- if (IS_PARTIAL_ARRAY(op1)) {
- MAKE_PARTIAL_ARRAY(&zv);
- }
- SET_RESULT(result, &zv);
- } else {
- SET_RESULT_BOT(result);
- }
- } else {
- SET_RESULT(result, &zv);
- }
- zval_ptr_dtor_nogc(&zv);
- }
- return;
- }
- case ZEND_ADD_ARRAY_UNPACK: {
- zval *result = &ctx->values[ssa_op->result_use];
- if (IS_BOT(result) || IS_BOT(op1)) {
- SET_RESULT_BOT(result);
- return;
- }
- SKIP_IF_TOP(result);
- SKIP_IF_TOP(op1);
- /* See comment for ADD_ARRAY_ELEMENT. */
- if (Z_TYPE_P(result) == IS_NULL) {
- SET_RESULT_BOT(result);
- return;
- }
- ZVAL_COPY_VALUE(&zv, result);
- ZVAL_NULL(result);
- if (ct_eval_add_array_unpack(&zv, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- } else {
- SET_RESULT_BOT(result);
- }
- zval_ptr_dtor_nogc(&zv);
- return;
- }
- case ZEND_NEW:
- if (ssa_op->result_def >= 0
- && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
- empty_partial_object(&zv);
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- } else {
- SET_RESULT_BOT(result);
- }
- return;
- case ZEND_ASSIGN_STATIC_PROP_REF:
- case ZEND_ASSIGN_OBJ_REF:
- /* Handled here because we also need to BOT the OP_DATA operand, while the generic
- * code below will not do so. */
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(op2);
- opline++;
- ssa_op++;
- SET_RESULT_BOT(op1);
- break;
- }
- if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) {
- /* If any operand is BOT, mark the result as BOT right away.
- * Exceptions to this rule are handled above. */
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(op2);
- return;
- }
- switch (opline->opcode) {
- case ZEND_ADD:
- case ZEND_SUB:
- case ZEND_MUL:
- case ZEND_DIV:
- case ZEND_MOD:
- case ZEND_POW:
- case ZEND_SL:
- case ZEND_SR:
- case ZEND_CONCAT:
- case ZEND_FAST_CONCAT:
- case ZEND_IS_EQUAL:
- case ZEND_IS_NOT_EQUAL:
- case ZEND_IS_SMALLER:
- case ZEND_IS_SMALLER_OR_EQUAL:
- case ZEND_IS_IDENTICAL:
- case ZEND_IS_NOT_IDENTICAL:
- case ZEND_BW_OR:
- case ZEND_BW_AND:
- case ZEND_BW_XOR:
- case ZEND_BOOL_XOR:
- case ZEND_CASE:
- case ZEND_CASE_STRICT:
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_binary_op(&zv, opline->opcode, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_ASSIGN_OP:
- case ZEND_ASSIGN_DIM_OP:
- case ZEND_ASSIGN_OBJ_OP:
- case ZEND_ASSIGN_STATIC_PROP_OP:
- if (op1) {
- SKIP_IF_TOP(op1);
- }
- if (op2) {
- SKIP_IF_TOP(op2);
- }
- if (opline->opcode == ZEND_ASSIGN_OP) {
- if (ct_eval_binary_op(&zv, opline->extended_value, op1, op2) == SUCCESS) {
- SET_RESULT(op1, &zv);
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- } else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
- if ((IS_PARTIAL_ARRAY(op1) || Z_TYPE_P(op1) == IS_ARRAY)
- && ssa_op->op1_def >= 0 && op2) {
- zval tmp;
- zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
- SKIP_IF_TOP(data);
- if (ct_eval_fetch_dim(&tmp, op1, op2, 0) == SUCCESS) {
- if (IS_BOT(data)) {
- dup_partial_array(&zv, op1);
- ct_eval_del_array_elem(&zv, op2);
- SET_RESULT_BOT(result);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- if (ct_eval_binary_op(&tmp, opline->extended_value, &tmp, data) != SUCCESS) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- zval_ptr_dtor_nogc(&tmp);
- break;
- }
- if (IS_PARTIAL_ARRAY(op1)) {
- dup_partial_array(&zv, op1);
- } else {
- ZVAL_COPY(&zv, op1);
- }
- if (ct_eval_assign_dim(&zv, &tmp, op2) == SUCCESS) {
- SET_RESULT(result, &tmp);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- }
- }
- } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
- if (op1 && IS_PARTIAL_OBJECT(op1)
- && ssa_op->op1_def >= 0
- && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
- zval tmp;
- zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
- SKIP_IF_TOP(data);
- if (ct_eval_fetch_obj(&tmp, op1, op2) == SUCCESS) {
- if (IS_BOT(data)) {
- dup_partial_object(&zv, op1);
- ct_eval_del_obj_prop(&zv, op2);
- SET_RESULT_BOT(result);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- if (ct_eval_binary_op(&tmp, opline->extended_value, &tmp, data) != SUCCESS) {
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- zval_ptr_dtor_nogc(&tmp);
- break;
- }
- dup_partial_object(&zv, op1);
- if (ct_eval_assign_obj(&zv, &tmp, op2) == SUCCESS) {
- SET_RESULT(result, &tmp);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- zval_ptr_dtor_nogc(&tmp);
- zval_ptr_dtor_nogc(&zv);
- }
- }
- }
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- break;
- case ZEND_PRE_INC_OBJ:
- case ZEND_PRE_DEC_OBJ:
- case ZEND_POST_INC_OBJ:
- case ZEND_POST_DEC_OBJ:
- if (op1) {
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (IS_PARTIAL_OBJECT(op1)
- && ssa_op->op1_def >= 0
- && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
- zval tmp1, tmp2;
- if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS
- && ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) {
- dup_partial_object(&zv, op1);
- ct_eval_assign_obj(&zv, &tmp2, op2);
- if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) {
- SET_RESULT(result, &tmp2);
- } else {
- SET_RESULT(result, &tmp1);
- }
- zval_ptr_dtor_nogc(&tmp1);
- zval_ptr_dtor_nogc(&tmp2);
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- }
- }
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(result);
- break;
- case ZEND_PRE_INC:
- case ZEND_PRE_DEC:
- SKIP_IF_TOP(op1);
- if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) {
- SET_RESULT(op1, &zv);
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(result);
- break;
- case ZEND_POST_INC:
- case ZEND_POST_DEC:
- SKIP_IF_TOP(op1);
- SET_RESULT(result, op1);
- if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) {
- SET_RESULT(op1, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(op1);
- break;
- case ZEND_BW_NOT:
- case ZEND_BOOL_NOT:
- SKIP_IF_TOP(op1);
- if (IS_PARTIAL_ARRAY(op1)) {
- SET_RESULT_BOT(result);
- break;
- }
- if (zend_optimizer_eval_unary_op(&zv, opline->opcode, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_CAST:
- SKIP_IF_TOP(op1);
- if (IS_PARTIAL_ARRAY(op1)) {
- SET_RESULT_BOT(result);
- break;
- }
- if (zend_optimizer_eval_cast(&zv, opline->extended_value, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_BOOL:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- SKIP_IF_TOP(op1);
- if (ct_eval_bool_cast(&zv, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_STRLEN:
- SKIP_IF_TOP(op1);
- if (zend_optimizer_eval_strlen(&zv, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_YIELD_FROM:
- // tmp = yield from [] -> tmp = null
- SKIP_IF_TOP(op1);
- if (Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0) {
- ZVAL_NULL(&zv);
- SET_RESULT(result, &zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_COUNT:
- SKIP_IF_TOP(op1);
- if (Z_TYPE_P(op1) == IS_ARRAY) {
- ZVAL_LONG(&zv, zend_hash_num_elements(Z_ARRVAL_P(op1)));
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_IN_ARRAY:
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_in_array(&zv, opline->extended_value, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_ARRAY_KEY_EXISTS:
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_array_key_exists(&zv, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_FETCH_DIM_R:
- case ZEND_FETCH_DIM_IS:
- case ZEND_FETCH_LIST_R:
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST_R)) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_ISSET_ISEMPTY_DIM_OBJ:
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_isset_dim(&zv, opline->extended_value, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_FETCH_OBJ_R:
- case ZEND_FETCH_OBJ_IS:
- if (op1) {
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_fetch_obj(&zv, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_ISSET_ISEMPTY_PROP_OBJ:
- if (op1) {
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_isset_obj(&zv, opline->extended_value, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_QM_ASSIGN:
- case ZEND_JMP_SET:
- case ZEND_COALESCE:
- case ZEND_COPY_TMP:
- SET_RESULT(result, op1);
- break;
- case ZEND_JMP_NULL:
- switch (opline->extended_value) {
- case ZEND_SHORT_CIRCUITING_CHAIN_EXPR:
- ZVAL_NULL(&zv);
- break;
- case ZEND_SHORT_CIRCUITING_CHAIN_ISSET:
- ZVAL_FALSE(&zv);
- break;
- case ZEND_SHORT_CIRCUITING_CHAIN_EMPTY:
- ZVAL_TRUE(&zv);
- break;
- EMPTY_SWITCH_DEFAULT_CASE()
- }
- SET_RESULT(result, &zv);
- break;
- #if 0
- case ZEND_FETCH_CLASS:
- if (!op1) {
- SET_RESULT_BOT(result);
- break;
- }
- SET_RESULT(result, op1);
- break;
- #endif
- case ZEND_ISSET_ISEMPTY_CV:
- SKIP_IF_TOP(op1);
- if (ct_eval_isset_isempty(&zv, opline->extended_value, op1) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_TYPE_CHECK:
- SKIP_IF_TOP(op1);
- ct_eval_type_check(&zv, opline->extended_value, op1);
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- case ZEND_INSTANCEOF:
- SKIP_IF_TOP(op1);
- ZVAL_FALSE(&zv);
- SET_RESULT(result, &zv);
- break;
- case ZEND_ROPE_INIT:
- SKIP_IF_TOP(op2);
- if (IS_PARTIAL_ARRAY(op2)) {
- SET_RESULT_BOT(result);
- break;
- }
- if (zend_optimizer_eval_cast(&zv, IS_STRING, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_ROPE_ADD:
- case ZEND_ROPE_END:
- // TODO The way this is currently implemented will result in quadratic runtime
- // This is not necessary, the way the algorithm works it's okay to reuse the same
- // string for all SSA vars with some extra checks
- SKIP_IF_TOP(op1);
- SKIP_IF_TOP(op2);
- if (ct_eval_binary_op(&zv, ZEND_CONCAT, op1, op2) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- SET_RESULT_BOT(result);
- break;
- case ZEND_DO_ICALL:
- {
- zend_call_info *call;
- zval *name, *args[3] = {NULL};
- int i;
- if (!ctx->call_map) {
- SET_RESULT_BOT(result);
- break;
- }
- call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
- name = CT_CONSTANT_EX(ctx->scdf.op_array, call->caller_init_opline->op2.constant);
- /* We already know it can't be evaluated, don't bother checking again */
- if (ssa_op->result_def < 0 || IS_BOT(&ctx->values[ssa_op->result_def])) {
- break;
- }
- /* We're only interested in functions with up to three arguments right now */
- if (call->num_args > 3 || call->send_unpack || call->is_prototype) {
- SET_RESULT_BOT(result);
- break;
- }
- for (i = 0; i < call->num_args; i++) {
- zend_op *opline = call->arg_info[i].opline;
- if (opline->opcode != ZEND_SEND_VAL && opline->opcode != ZEND_SEND_VAR) {
- SET_RESULT_BOT(result);
- return;
- }
- args[i] = get_op1_value(ctx, opline,
- &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
- if (args[i]) {
- if (IS_BOT(args[i]) || IS_PARTIAL_ARRAY(args[i])) {
- SET_RESULT_BOT(result);
- return;
- } else if (IS_TOP(args[i])) {
- return;
- }
- }
- }
- /* We didn't get a BOT argument, so value stays the same */
- if (!IS_TOP(&ctx->values[ssa_op->result_def])) {
- break;
- }
- if (ct_eval_func_call(scdf->op_array, &zv, Z_STR_P(name), call->num_args, args) == SUCCESS) {
- SET_RESULT(result, &zv);
- zval_ptr_dtor_nogc(&zv);
- break;
- }
- #if 0
- /* sort out | uniq -c | sort -n */
- fprintf(stderr, "%s\n", Z_STRVAL_P(name));
- /*if (args[1]) {
- php_printf("%s %Z %Z\n", Z_STRVAL_P(name), args[0], args[1]);
- } else {
- php_printf("%s %Z\n", Z_STRVAL_P(name), args[0]);
- }*/
- #endif
- SET_RESULT_BOT(result);
- break;
- }
- default:
- {
- /* If we have no explicit implementation return BOT */
- SET_RESULT_BOT(result);
- SET_RESULT_BOT(op1);
- SET_RESULT_BOT(op2);
- break;
- }
- }
- }
- /* Returns whether there is a successor */
- static void sccp_mark_feasible_successors(
- scdf_ctx *scdf,
- int block_num, zend_basic_block *block,
- zend_op *opline, zend_ssa_op *ssa_op) {
- sccp_ctx *ctx = (sccp_ctx *) scdf;
- zval *op1, zv;
- int s;
- /* We can't determine the branch target at compile-time for these */
- switch (opline->opcode) {
- case ZEND_ASSERT_CHECK:
- case ZEND_CATCH:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
- scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
- return;
- }
- op1 = get_op1_value(ctx, opline, ssa_op);
- /* Branch target can be either one */
- if (!op1 || IS_BOT(op1)) {
- for (s = 0; s < block->successors_count; s++) {
- scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
- }
- return;
- }
- /* Branch target not yet known */
- if (IS_TOP(op1)) {
- return;
- }
- switch (opline->opcode) {
- case ZEND_JMPZ:
- case ZEND_JMPZNZ:
- case ZEND_JMPZ_EX:
- {
- if (ct_eval_bool_cast(&zv, op1) == FAILURE) {
- scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
- scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
- return;
- }
- s = Z_TYPE(zv) == IS_TRUE;
- break;
- }
- case ZEND_JMPNZ:
- case ZEND_JMPNZ_EX:
- case ZEND_JMP_SET:
- {
- if (ct_eval_bool_cast(&zv, op1) == FAILURE) {
- scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
- scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
- return;
- }
- s = Z_TYPE(zv) == IS_FALSE;
- break;
- }
- case ZEND_COALESCE:
- s = (Z_TYPE_P(op1) == IS_NULL);
- break;
- case ZEND_JMP_NULL:
- s = (Z_TYPE_P(op1) != IS_NULL);
- break;
- case ZEND_FE_RESET_R:
- case ZEND_FE_RESET_RW:
- /* A non-empty partial array is definitely non-empty, but an
- * empty partial array may be non-empty at runtime. */
- if (Z_TYPE_P(op1) != IS_ARRAY ||
- (IS_PARTIAL_ARRAY(op1) && zend_hash_num_elements(Z_ARR_P(op1)) == 0)) {
- scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
- scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
- return;
- }
- s = zend_hash_num_elements(Z_ARR_P(op1)) != 0;
- break;
- case ZEND_SWITCH_LONG:
- case ZEND_SWITCH_STRING:
- case ZEND_MATCH:
- {
- bool strict_comparison = opline->opcode == ZEND_MATCH;
- zend_uchar type = Z_TYPE_P(op1);
- bool correct_type =
- (opline->opcode == ZEND_SWITCH_LONG && type == IS_LONG)
- || (opline->opcode == ZEND_SWITCH_STRING && type == IS_STRING)
- || (opline->opcode == ZEND_MATCH && (type == IS_LONG || type == IS_STRING));
- if (correct_type) {
- zend_op_array *op_array = scdf->op_array;
- zend_ssa *ssa = scdf->ssa;
- HashTable *jmptable = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant));
- zval *jmp_zv = type == IS_LONG
- ? zend_hash_index_find(jmptable, Z_LVAL_P(op1))
- : zend_hash_find(jmptable, Z_STR_P(op1));
- int target;
- if (jmp_zv) {
- target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(jmp_zv))];
- } else {
- target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
- }
- scdf_mark_edge_feasible(scdf, block_num, target);
- return;
- } else if (strict_comparison) {
- zend_op_array *op_array = scdf->op_array;
- zend_ssa *ssa = scdf->ssa;
- int target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
- scdf_mark_edge_feasible(scdf, block_num, target);
- return;
- }
- s = block->successors_count - 1;
- break;
- }
- default:
- for (s = 0; s < block->successors_count; s++) {
- scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
- }
- return;
- }
- scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
- }
- static void join_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2)
- {
- zend_ulong index;
- zend_string *key;
- zval *val1, *val2;
- ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) {
- if (key) {
- val2 = zend_hash_find(ht2, key);
- } else {
- val2 = zend_hash_index_find(ht2, index);
- }
- if (val2 && zend_is_identical(val1, val2)) {
- if (key) {
- val1 = zend_hash_add_new(ret, key, val1);
- } else {
- val1 = zend_hash_index_add_new(ret, index, val1);
- }
- Z_TRY_ADDREF_P(val1);
- }
- } ZEND_HASH_FOREACH_END();
- }
- static int join_partial_arrays(zval *a, zval *b)
- {
- zval ret;
- if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a))
- || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) {
- return FAILURE;
- }
- empty_partial_array(&ret);
- join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b));
- zval_ptr_dtor_nogc(a);
- ZVAL_COPY_VALUE(a, &ret);
- return SUCCESS;
- }
- static int join_partial_objects(zval *a, zval *b)
- {
- zval ret;
- if (!IS_PARTIAL_OBJECT(a) || !IS_PARTIAL_OBJECT(b)) {
- return FAILURE;
- }
- empty_partial_object(&ret);
- join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b));
- zval_ptr_dtor_nogc(a);
- ZVAL_COPY_VALUE(a, &ret);
- return SUCCESS;
- }
- static void join_phi_values(zval *a, zval *b, bool escape) {
- if (IS_BOT(a) || IS_TOP(b)) {
- return;
- }
- if (IS_TOP(a)) {
- zval_ptr_dtor_nogc(a);
- ZVAL_COPY(a, b);
- return;
- }
- if (IS_BOT(b)) {
- zval_ptr_dtor_nogc(a);
- MAKE_BOT(a);
- return;
- }
- if (IS_PARTIAL_ARRAY(a) || IS_PARTIAL_ARRAY(b)) {
- if (join_partial_arrays(a, b) != SUCCESS) {
- zval_ptr_dtor_nogc(a);
- MAKE_BOT(a);
- }
- } else if (IS_PARTIAL_OBJECT(a) || IS_PARTIAL_OBJECT(b)) {
- if (escape || join_partial_objects(a, b) != SUCCESS) {
- zval_ptr_dtor_nogc(a);
- MAKE_BOT(a);
- }
- } else if (!zend_is_identical(a, b)) {
- if (join_partial_arrays(a, b) != SUCCESS) {
- zval_ptr_dtor_nogc(a);
- MAKE_BOT(a);
- }
- }
- }
- static void sccp_visit_phi(scdf_ctx *scdf, zend_ssa_phi *phi) {
- sccp_ctx *ctx = (sccp_ctx *) scdf;
- zend_ssa *ssa = scdf->ssa;
- ZEND_ASSERT(phi->ssa_var >= 0);
- if (!IS_BOT(&ctx->values[phi->ssa_var])) {
- zend_basic_block *block = &ssa->cfg.blocks[phi->block];
- int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
- int i;
- zval result;
- MAKE_TOP(&result);
- #if SCP_DEBUG
- fprintf(stderr, "Handling phi(");
- #endif
- if (phi->pi >= 0) {
- ZEND_ASSERT(phi->sources[0] >= 0);
- if (scdf_is_edge_feasible(scdf, phi->pi, phi->block)) {
- join_phi_values(&result, &ctx->values[phi->sources[0]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE);
- }
- } else {
- for (i = 0; i < block->predecessors_count; i++) {
- ZEND_ASSERT(phi->sources[i] >= 0);
- if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) {
- #if SCP_DEBUG
- scp_dump_value(&ctx->values[phi->sources[i]]);
- fprintf(stderr, ",");
- #endif
- join_phi_values(&result, &ctx->values[phi->sources[i]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE);
- } else {
- #if SCP_DEBUG
- fprintf(stderr, " --,");
- #endif
- }
- }
- }
- #if SCP_DEBUG
- fprintf(stderr, ")\n");
- #endif
- set_value(scdf, ctx, phi->ssa_var, &result);
- zval_ptr_dtor_nogc(&result);
- }
- }
- static zval *value_from_type_and_range(sccp_ctx *ctx, int var_num, zval *tmp) {
- zend_ssa *ssa = ctx->scdf.ssa;
- zend_ssa_var_info *info = &ssa->var_info[var_num];
- if (info->type & MAY_BE_UNDEF) {
- return NULL;
- }
- if (!(info->type & MAY_BE_ANY)) {
- /* This code must be unreachable. We could replace operands with NULL, but this doesn't
- * really make things better. It would be better to later remove this code entirely. */
- return NULL;
- }
- if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_NULL))) {
- ZVAL_NULL(tmp);
- return tmp;
- }
- if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_FALSE))) {
- ZVAL_FALSE(tmp);
- return tmp;
- }
- if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_TRUE))) {
- ZVAL_TRUE(tmp);
- return tmp;
- }
- if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))
- && info->has_range
- && !info->range.overflow && !info->range.underflow
- && info->range.min == info->range.max) {
- ZVAL_LONG(tmp, info->range.min);
- return tmp;
- }
- return NULL;
- }
- /* Call instruction -> remove opcodes that are part of the call */
- static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op)
- {
- zend_ssa *ssa = ctx->scdf.ssa;
- zend_op_array *op_array = ctx->scdf.op_array;
- zend_call_info *call;
- int i;
- ZEND_ASSERT(ctx->call_map);
- call = ctx->call_map[opline - op_array->opcodes];
- ZEND_ASSERT(call);
- ZEND_ASSERT(call->caller_call_opline == opline);
- zend_ssa_remove_instr(ssa, opline, ssa_op);
- zend_ssa_remove_instr(ssa, call->caller_init_opline,
- &ssa->ops[call->caller_init_opline - op_array->opcodes]);
- for (i = 0; i < call->num_args; i++) {
- zend_ssa_remove_instr(ssa, call->arg_info[i].opline,
- &ssa->ops[call->arg_info[i].opline - op_array->opcodes]);
- }
- // TODO: remove call_info completely???
- call->callee_func = NULL;
- return call->num_args + 2;
- }
- /* This is a basic DCE pass we run after SCCP. It only works on those instructions those result
- * value(s) were determined by SCCP. It removes dead computational instructions and converts
- * CV-affecting instructions into CONST ASSIGNs. This basic DCE is performed for multiple reasons:
- * a) During operand replacement we eliminate FREEs. The corresponding computational instructions
- * must be removed to avoid leaks. This way SCCP can run independently of the full DCE pass.
- * b) The main DCE pass relies on type analysis to determine whether instructions have side-effects
- * and can't be DCEd. This means that it will not be able collect all instructions rendered dead
- * by SCCP, because they may have potentially side-effecting types, but the actual values are
- * not. As such doing DCE here will allow us to eliminate more dead code in combination.
- * c) The ordinary DCE pass cannot collect dead calls. However SCCP can result in dead calls, which
- * we need to collect.
- * d) The ordinary DCE pass cannot collect construction of dead non-escaping arrays and objects.
- */
- static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var, zval *value)
- {
- zend_ssa *ssa = ctx->scdf.ssa;
- zend_op_array *op_array = ctx->scdf.op_array;
- int removed_ops = 0;
- if (var->definition >= 0) {
- zend_op *opline = &op_array->opcodes[var->definition];
- zend_ssa_op *ssa_op = &ssa->ops[var->definition];
- if (ssa_op->result_def == var_num) {
- if (opline->opcode == ZEND_ASSIGN) {
- /* We can't drop the ASSIGN, but we can remove the result. */
- if (var->use_chain < 0 && var->phi_use_chain == NULL) {
- opline->result_type = IS_UNUSED;
- zend_ssa_remove_result_def(ssa, ssa_op);
- }
- return 0;
- }
- if (ssa_op->op1_def >= 0 || ssa_op->op2_def >= 0) {
- if (var->use_chain < 0 && var->phi_use_chain == NULL) {
- switch (opline->opcode) {
- case ZEND_ASSIGN:
- case ZEND_ASSIGN_REF:
- case ZEND_ASSIGN_DIM:
- case ZEND_ASSIGN_OBJ:
- case ZEND_ASSIGN_OBJ_REF:
- case ZEND_ASSIGN_STATIC_PROP:
- case ZEND_ASSIGN_STATIC_PROP_REF:
- case ZEND_ASSIGN_OP:
- case ZEND_ASSIGN_DIM_OP:
- case ZEND_ASSIGN_OBJ_OP:
- case ZEND_ASSIGN_STATIC_PROP_OP:
- case ZEND_PRE_INC:
- case ZEND_PRE_DEC:
- case ZEND_PRE_INC_OBJ:
- case ZEND_PRE_DEC_OBJ:
- case ZEND_DO_ICALL:
- case ZEND_DO_UCALL:
- case ZEND_DO_FCALL_BY_NAME:
- case ZEND_DO_FCALL:
- case ZEND_INCLUDE_OR_EVAL:
- case ZEND_YIELD:
- case ZEND_YIELD_FROM:
- case ZEND_ASSERT_CHECK:
- opline->result_type = IS_UNUSED;
- zend_ssa_remove_result_def(ssa, ssa_op);
- break;
- default:
- break;
- }
- }
- /* we cannot remove instruction that defines other variables */
- return 0;
- } else if (opline->opcode == ZEND_JMPZ_EX
- || opline->opcode == ZEND_JMPNZ_EX
- || opline->opcode == ZEND_JMP_SET
- || opline->opcode == ZEND_COALESCE
- || opline->opcode == ZEND_JMP_NULL
- || opline->opcode == ZEND_FE_RESET_R
- || opline->opcode == ZEND_FE_RESET_RW
- || opline->opcode == ZEND_FE_FETCH_R
- || opline->opcode == ZEND_FE_FETCH_RW
- || opline->opcode == ZEND_NEW) {
- /* we cannot simple remove jump instructions */
- return 0;
- } else if (var->use_chain >= 0
- || var->phi_use_chain != NULL) {
- if (value
- && (opline->result_type & (IS_VAR|IS_TMP_VAR))
- && opline->opcode != ZEND_QM_ASSIGN
- && opline->opcode != ZEND_ROPE_INIT
- && opline->opcode != ZEND_ROPE_ADD
- && opline->opcode != ZEND_INIT_ARRAY
- && opline->opcode != ZEND_ADD_ARRAY_ELEMENT
- && opline->opcode != ZEND_ADD_ARRAY_UNPACK) {
- /* Replace with QM_ASSIGN */
- zend_uchar old_type = opline->result_type;
- uint32_t old_var = opline->result.var;
- ssa_op->result_def = -1;
- if (opline->opcode == ZEND_DO_ICALL) {
- removed_ops = remove_call(ctx, opline, ssa_op) - 1;
- } else {
- zend_ssa_remove_instr(ssa, opline, ssa_op);
- }
- ssa_op->result_def = var_num;
- opline->opcode = ZEND_QM_ASSIGN;
- opline->result_type = old_type;
- opline->result.var = old_var;
- Z_TRY_ADDREF_P(value);
- zend_optimizer_update_op1_const(ctx->scdf.op_array, opline, value);
- }
- return 0;
- } else if ((opline->op2_type & (IS_VAR|IS_TMP_VAR))
- && (!value_known(&ctx->values[ssa_op->op2_use])
- || IS_PARTIAL_ARRAY(&ctx->values[ssa_op->op2_use])
- || IS_PARTIAL_OBJECT(&ctx->values[ssa_op->op2_use]))) {
- return 0;
- } else if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
- && (!value_known(&ctx->values[ssa_op->op1_use])
- || IS_PARTIAL_ARRAY(&ctx->values[ssa_op->op1_use])
- || IS_PARTIAL_OBJECT(&ctx->values[ssa_op->op1_use]))) {
- if (opline->opcode == ZEND_TYPE_CHECK
- || opline->opcode == ZEND_BOOL) {
- zend_ssa_remove_result_def(ssa, ssa_op);
- /* For TYPE_CHECK we may compute the result value without knowing the
- * operand, based on type inference information. Make sure the operand is
- * freed and leave further cleanup to DCE. */
- opline->opcode = ZEND_FREE;
- opline->result_type = IS_UNUSED;
- removed_ops++;
- } else {
- return 0;
- }
- } else {
- zend_ssa_remove_result_def(ssa, ssa_op);
- if (opline->opcode == ZEND_DO_ICALL) {
- removed_ops = remove_call(ctx, opline, ssa_op);
- } else {
- zend_ssa_remove_instr(ssa, opline, ssa_op);
- removed_ops++;
- }
- }
- } else if (ssa_op->op1_def == var_num) {
- if (opline->opcode == ZEND_ASSIGN) {
- /* Leave assigns to DCE (due to dtor effects) */
- return 0;
- }
- /* Compound assign or incdec -> convert to direct ASSIGN */
- if (!value) {
- /* In some cases zend_may_throw() may be avoided */
- switch (opline->opcode) {
- case ZEND_ASSIGN_DIM:
- case ZEND_ASSIGN_OBJ:
- case ZEND_ASSIGN_OP:
- case ZEND_ASSIGN_DIM_OP:
- case ZEND_ASSIGN_OBJ_OP:
- case ZEND_ASSIGN_STATIC_PROP_OP:
- if ((ssa_op->op2_use >= 0 && !value_known(&ctx->values[ssa_op->op2_use]))
- || ((ssa_op+1)->op1_use >= 0 &&!value_known(&ctx->values[(ssa_op+1)->op1_use]))) {
- return 0;
- }
- break;
- case ZEND_PRE_INC_OBJ:
- case ZEND_PRE_DEC_OBJ:
- case ZEND_POST_INC_OBJ:
- case ZEND_POST_DEC_OBJ:
- if (ssa_op->op2_use >= 0 && !value_known(&ctx->values[ssa_op->op2_use])) {
- return 0;
- }
- break;
- case ZEND_INIT_ARRAY:
- case ZEND_ADD_ARRAY_ELEMENT:
- if (opline->op2_type == IS_UNUSED) {
- return 0;
- }
- /* break missing intentionally */
- default:
- if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
- return 0;
- }
- break;
- }
- }
- /* Mark result unused, if possible */
- if (ssa_op->result_def >= 0) {
- if (ssa->vars[ssa_op->result_def].use_chain < 0
- && ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
- zend_ssa_remove_result_def(ssa, ssa_op);
- opline->result_type = IS_UNUSED;
- } else if (opline->opcode != ZEND_PRE_INC &&
- opline->opcode != ZEND_PRE_DEC) {
- /* op1_def and result_def are different */
- return removed_ops;
- }
- }
- /* Destroy previous op2 */
- if (opline->op2_type == IS_CONST) {
- literal_dtor(&ZEND_OP2_LITERAL(opline));
- } else if (ssa_op->op2_use >= 0) {
- if (ssa_op->op2_use != ssa_op->op1_use) {
- zend_ssa_unlink_use_chain(ssa, var->definition, ssa_op->op2_use);
- }
- ssa_op->op2_use = -1;
- ssa_op->op2_use_chain = -1;
- }
- /* Remove OP_DATA opcode */
- switch (opline->opcode) {
- case ZEND_ASSIGN_DIM:
- case ZEND_ASSIGN_OBJ:
- removed_ops++;
- zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
- break;
- case ZEND_ASSIGN_DIM_OP:
- case ZEND_ASSIGN_OBJ_OP:
- case ZEND_ASSIGN_STATIC_PROP_OP:
- removed_ops++;
- zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
- break;
- default:
- break;
- }
- if (value) {
- /* Convert to ASSIGN */
- opline->opcode = ZEND_ASSIGN;
- opline->op2_type = IS_CONST;
- opline->op2.constant = zend_optimizer_add_literal(op_array, value);
- Z_TRY_ADDREF_P(value);
- } else {
- /* Remove dead array or object construction */
- removed_ops++;
- if (var->use_chain >= 0 || var->phi_use_chain != NULL) {
- zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, ssa_op->op1_use, 1);
- }
- zend_ssa_remove_op1_def(ssa, ssa_op);
- zend_ssa_remove_instr(ssa, opline, ssa_op);
- }
- }
- } else if (var->definition_phi
- && var->use_chain < 0
- && var->phi_use_chain == NULL) {
- zend_ssa_remove_phi(ssa, var->definition_phi);
- }
- return removed_ops;
- }
- /* This will try to replace uses of SSA variables we have determined to be constant. Not all uses
- * can be replaced, because some instructions don't accept constant operands or only accept them
- * if they have a certain type. */
- static int replace_constant_operands(sccp_ctx *ctx) {
- zend_ssa *ssa = ctx->scdf.ssa;
- zend_op_array *op_array = ctx->scdf.op_array;
- int i;
- zval tmp;
- int removed_ops = 0;
- /* We iterate the variables backwards, so we can eliminate sequences like INIT_ROPE
- * and INIT_ARRAY. */
- for (i = ssa->vars_count - 1; i >= op_array->last_var; i--) {
- zend_ssa_var *var = &ssa->vars[i];
- zval *value;
- int use;
- if (IS_PARTIAL_ARRAY(&ctx->values[i])
- || IS_PARTIAL_OBJECT(&ctx->values[i])) {
- if (!Z_DELREF(ctx->values[i])) {
- zend_array_destroy(Z_ARR(ctx->values[i]));
- }
- MAKE_BOT(&ctx->values[i]);
- if ((var->use_chain < 0 && var->phi_use_chain == NULL) || var->no_val) {
- removed_ops += try_remove_definition(ctx, i, var, NULL);
- }
- continue;
- } else if (value_known(&ctx->values[i])) {
- value = &ctx->values[i];
- } else {
- value = value_from_type_and_range(ctx, i, &tmp);
- if (!value) {
- continue;
- }
- }
- FOREACH_USE(var, use) {
- zend_op *opline = &op_array->opcodes[use];
- zend_ssa_op *ssa_op = &ssa->ops[use];
- if (try_replace_op1(ctx, opline, ssa_op, i, value)) {
- if (opline->opcode == ZEND_NOP) {
- removed_ops++;
- }
- ZEND_ASSERT(ssa_op->op1_def == -1);
- if (ssa_op->op1_use != ssa_op->op2_use) {
- zend_ssa_unlink_use_chain(ssa, use, ssa_op->op1_use);
- } else {
- ssa_op->op2_use_chain = ssa_op->op1_use_chain;
- }
- ssa_op->op1_use = -1;
- ssa_op->op1_use_chain = -1;
- }
- if (try_replace_op2(ctx, opline, ssa_op, i, value)) {
- ZEND_ASSERT(ssa_op->op2_def == -1);
- if (ssa_op->op2_use != ssa_op->op1_use) {
- zend_ssa_unlink_use_chain(ssa, use, ssa_op->op2_use);
- }
- ssa_op->op2_use = -1;
- ssa_op->op2_use_chain = -1;
- }
- } FOREACH_USE_END();
- if (value_known(&ctx->values[i])) {
- removed_ops += try_remove_definition(ctx, i, var, value);
- }
- }
- return removed_ops;
- }
- static void sccp_context_init(zend_optimizer_ctx *ctx, sccp_ctx *sccp,
- zend_ssa *ssa, zend_op_array *op_array, zend_call_info **call_map) {
- int i;
- sccp->call_map = call_map;
- sccp->values = zend_arena_alloc(&ctx->arena, sizeof(zval) * ssa->vars_count);
- MAKE_TOP(&sccp->top);
- MAKE_BOT(&sccp->bot);
- i = 0;
- for (; i < op_array->last_var; ++i) {
- /* These are all undefined variables, which we have to mark BOT.
- * Otherwise the undefined variable warning might not be preserved. */
- MAKE_BOT(&sccp->values[i]);
- }
- for (; i < ssa->vars_count; ++i) {
- if (ssa->vars[i].alias) {
- MAKE_BOT(&sccp->values[i]);
- } else {
- MAKE_TOP(&sccp->values[i]);
- }
- }
- }
- static void sccp_context_free(sccp_ctx *sccp) {
- int i;
- for (i = sccp->scdf.op_array->last_var; i < sccp->scdf.ssa->vars_count; ++i) {
- zval_ptr_dtor_nogc(&sccp->values[i]);
- }
- }
- int sccp_optimize_op_array(zend_optimizer_ctx *ctx, zend_op_array *op_array, zend_ssa *ssa, zend_call_info **call_map)
- {
- sccp_ctx sccp;
- int removed_ops = 0;
- void *checkpoint = zend_arena_checkpoint(ctx->arena);
- sccp_context_init(ctx, &sccp, ssa, op_array, call_map);
- sccp.scdf.handlers.visit_instr = sccp_visit_instr;
- sccp.scdf.handlers.visit_phi = sccp_visit_phi;
- sccp.scdf.handlers.mark_feasible_successors = sccp_mark_feasible_successors;
- scdf_init(ctx, &sccp.scdf, op_array, ssa);
- scdf_solve(&sccp.scdf, "SCCP");
- if (ctx->debug_level & ZEND_DUMP_SCCP) {
- int i, first = 1;
- for (i = op_array->last_var; i < ssa->vars_count; i++) {
- zval *zv = &sccp.values[i];
- if (IS_TOP(zv) || IS_BOT(zv)) {
- continue;
- }
- if (first) {
- first = 0;
- fprintf(stderr, "\nSCCP Values for \"");
- zend_dump_op_array_name(op_array);
- fprintf(stderr, "\":\n");
- }
- fprintf(stderr, " #%d.", i);
- zend_dump_var(op_array, IS_CV, ssa->vars[i].var);
- fprintf(stderr, " =");
- scp_dump_value(zv);
- fprintf(stderr, "\n");
- }
- }
- removed_ops += scdf_remove_unreachable_blocks(&sccp.scdf);
- removed_ops += replace_constant_operands(&sccp);
- sccp_context_free(&sccp);
- zend_arena_release(&ctx->arena, checkpoint);
- return removed_ops;
- }
|