静态分析-RIPS-源码解析记录-01

token流扫描重构部分,这一部分主要利用php的token解析api解析出来的token流,对其中的特定token进行删除、替换、对于特定的语法结构进行重构,保持php语法结构上的一致性

解析主要在lib/scanner.php中通过Tokenizer这个类来实现,也就是在main.php中开始调用new scanner的对象,准备开始扫描漏洞,此时在scanner的构造函数中完成token流的解析

这里主要在scanner这个类的构造函数里面完整待扫描的文件的token解析,主要由以下四个步骤完成

接着到tokenize中进行具体的token解析,这里要借助php zend引擎自带的一个词法分析的函数,token_get_all();

 

 对于上面这个文件,解析以后如下图所示:

array(11) {
  [0] =>
  array(3) {
    [0] =>
    int(379)
    [1] =>
    string(6) "<?php
"
    [2] =>
    int(1)
  }
  [1] =>
  array(3) {
    [0] =>
    int(320)
    [1] =>
    string(2) "$a"
    [2] =>
    int(2)
  }
  [2] =>
  string(1) "="
  [3] =>
  string(1) "["
  [4] =>
  array(3) {
    [0] =>
    int(323)
    [1] =>
    string(3) ""a""
    [2] =>
    int(2)
  }
  [5] =>
  string(1) ","
  [6] =>
  array(3) {
    [0] =>
    int(323)
    [1] =>
    string(3) ""b""
    [2] =>
    int(2)
  }
  [7] =>
  string(1) "]"
  [8] =>
  string(1) ";"
  [9] =>
  array(3) {
    [0] =>
    int(382)
    [1] =>
    string(1) "
"
    [2] =>
    int(2)
  }
  [10] =>
  array(3) {
    [0] =>
    int(381)
    [1] =>
    string(3) "?>
"
    [2] =>
    int(3)
  }
}

解析出来的每个token以数组形式存在,默认一个token数组包括三个元素,0代表token常量,1代表token值,2代表行号,此时得到的是最初的token组,接下来要经过一系列的token处理,以为php中实际上变量表示方法多样,要做到统一才方便后面分析。

第一部分: 

prepare_tokens:

 function prepare_tokens()
    {    
        // delete whitespaces and other unimportant tokens, rewrite some special tokens
        for($i=0, $max=count($this->tokens); $i<$max; $i++) //遍历token数组
        {
            if( is_array($this->tokens[$i]) )  //a. unset掉可忽略token,比如php的开始标签<?php以及一些空格,比如if   ()变为if(),方便后面进行条件语句的解析,以及像if(): xxx endif 中间的文本unset掉
 b. 闭合标签变分号;  c.<?= 标签表echo
            {
                if( in_array($this->tokens[$i][0], Tokens::$T_IGNORE) )
                    unset($this->tokens[$i]);
                else if( $this->tokens[$i][0] === T_CLOSE_TAG )
                    $this->tokens[$i] = ';';    
                else if( $this->tokens[$i][0] === T_OPEN_TAG_WITH_ECHO )
                    $this->tokens[$i][1] = 'echo';
            } 
            // @ (depress errors) disturbs connected token handling
            else if($this->tokens[$i] === '@')  //unset掉@符号
            {
                unset($this->tokens[$i]);
            }    
            // rewrite $array{index} to $array[index]  //对于数组处理如果当前是花括号并且前一个token是变量
            else if( $this->tokens[$i] === '{'
            && isset($this->tokens[$i-1]) && ((is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE)
            || $this->tokens[$i-1] === ']') ) //或者上一个token是[,当前是{,则肯定是数组变量(主要是多维数组)
            {
                $this->tokens[$i] = '['; //则令当前token为左方括号
                $f=1;
                while($this->tokens[$i+$f] !== '}') //此时while循环找下一个与当前花括号对应的右花括号
                {
                    $f++;
                    if(!isset($this->tokens[$i+$f]))
                    {
                        addError('Could not find closing brace of '.$this->tokens[$i-1][1].'{}.', array_slice($this->tokens, $i-1, 2), $this->tokens[$i-1][2], $this->filename);
                        break;    //没找到则退出,说明语法有问题
                    }
                }
                $this->tokens[$i+$f] = ']';  //否则令右花括号为]
            }    
        }
        
        // rearranged key index of tokens
        $this->tokens = array_values($this->tokens);
    }    

第二部分:

接着是对多维数组的重构:

// rewrite $arrays[] to $variables and save keys in $tokens[$i][3]

从代码注释可以看出这个函数将多维数组的所有键名保存在当前解析为变量token的数组第四个元素中

function array_reconstruct_tokens()
    {    
        for($i=0,$max=count($this->tokens); $i<$max; $i++) //遍历所有token数组
        {
            // check for arrays
            if( is_array($this->tokens[$i]) && $this->tokens[$i][0] === T_VARIABLE && $this->tokens[$i+1] === '[' ) //当前token是个变量,并且下一个token是[,则最少即为一维数组
            {    
                $this->tokens[$i][3] = array(); //初始化第四个元素为数组
                $has_more_keys = true;  
                $index = -1;
                $c=2;
                
                // loop until no more index found: array[1][2][3]
                while($has_more_keys && $index < MAX_ARRAY_KEYS) //while循环遍历多维数组,max默认为10(这个数已经够了)
                {
                    $index++;
                    // save constant index as constant   //找到当前变量对应的右括号,主要是针对常量
                    if(($this->tokens[$i+$c][0] === T_CONSTANT_ENCAPSED_STRING || $this->tokens[$i+$c][0] === T_LNUMBER || $this->tokens[$i+$c][0] === T_NUM_STRING || $this->tokens[$i+$c][0] === T_STRING) && $this->tokens[$i+$c+1] === ']')
                    {         
                        unset($this->tokens[$i+$c-1]); //unset掉左括号
                        $this->tokens[$i][3][$index] = str_replace(array('"', "'"), '', $this->tokens[$i+$c][1]); //把键名放到第四个数组元素中
                        unset($this->tokens[$i+$c]); //unset掉键名
                        unset($this->tokens[$i+$c+1]); //unset掉右括号
                        $c+=2; #c+2尝试找到下一维数组
                    // save tokens of non-constant index as token-array for backtrace later  //$a[$b][][]对于这种非常量索引的情况  
                    } else
                    {
                        $this->tokens[$i][3][$index] = array(); 
                        $newbraceopen = 1; //就当作是左括号的个数
                        unset($this->tokens[$i+$c-1]); //unset掉左括号
                        while($newbraceopen !== 0) 
                        {    
                            if( $this->tokens[$i+$c] === '[' )
                            {
                                $newbraceopen++;  //哇,又遇到新的一个左括号
                            }
                            else if( $this->tokens[$i+$c] === ']' ) 
                            {
                                $newbraceopen--; //此时说明一组左右括号遍历完
                            }
                            else
                            {
                                $this->tokens[$i][3][$index][] = $this->tokens[$i+$c]; //此时将变量索引对应的数组保存在第四个元素中 
                            }    
                            unset($this->tokens[$i+$c]); //unset掉该变量索引或unset掉右括号或左括号
                            $c++; //就把它当作游标吧,游标不断滑动
                            
                            if(!isset($this->tokens[$i+$c])) //尝试找到=或者分号,实际就是结束当前数组的符号,没有找到则break退出
                            {
                                addError('Could not find closing bracket of '.$this->tokens[$i][1].'[].', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);
                                break;    
                            }
                        }
                        unset($this->tokens[$i+$c-1]); //这一处unset为了处理特殊情况
                    }
                    if($this->tokens[$i+$c] !== '[')
                        $has_more_keys = false;
                    $c++;    
                }    
                
                $i+=$c-1;
            }
        }
    
        // return tokens with rearranged key index
        $this->tokens = array_values($this->tokens);        
    }

 比如对于下面这种索引为变量的数组:

 解析以后将所有维度的键名存储在$a这个token的第四个数组元素中

 

 这个算法设计的还是挺巧妙的,每一处unset都设计的很恰当

 

 这个unset就刚刚假设当前游标为数组索引或右括号,-1直接unset掉左括号

 

 ①处当遍历数组变量完成后则置为true,此时因为游标在=或者;处,因此c++完后$i应该+c-1回到=或者;处

对于$a[$b][][]处理完后就是:

 

第三部分:

fix_tokens:

这一部分主要是重构一些token信息

function fix_tokens()
    {    
        for($i=0; $i<($max=count($this->tokens)); $i++)
        {
        // convert `backticks` to backticks()  #处理反引号
            if( $this->tokens[$i] === '`' )
            {        
                $f=1;
                while( $this->tokens[$i+$f] !== '`' )  #通过while循环来,将`xxx` 转换成backticks标识的token
                {    
                    // get line_nr of any near token
                    if( is_array($this->tokens[$i+$f]) )
                        $line_nr = $this->tokens[$i+$f][2];  #此时反引号中间内容的行号

                    $f++; #f++走到右反引号
                    if(!isset($this->tokens[$i+$f]) || $this->tokens[$i+$f] === ';')  #无闭合则报错
                    {
                        addError('Could not find closing backtick `.', array_slice($this->tokens, $i, 5), $this->tokens[$i+1][2], $this->filename);
                        break;    
                    }
                }
                if(!empty($line_nr)) #若反引号中间内容不为空,则进行重构
                { 
                    $this->tokens[$i+$f] = ')'; #将右引号变为圆括号)
                    $this->tokens[$i] = array(T_STRING, 'backticks', $line_nr);  #将左反引号声明一个backticks的token
                
                    // add element backticks() to array             
                    $this->tokens = array_merge(   #对当前token进行重构
                        array_slice($this->tokens, 0, $i+1), array('('),  #因为刚才将左反引号替换了,所以此时需要再添加一个左圆括号
                        array_slice($this->tokens, $i+1) #拼接上后面从右圆括号开始的token,所以就是`xxx`  变为 backtricks(xxx)
                    );    
                }

            }
        #接下来要对一些条件语句、循环语句进行解析,主要为IF,else if,for,foreach,while
        // real token
            else if( is_array($this->tokens[$i]) )  
            {    
            // rebuild if-clauses, for(), foreach(), while() without { } #首先重构没有花括号的,即只有方法体只有单条语句
                if ( ($this->tokens[$i][0] === T_IF || $this->tokens[$i][0] === T_ELSEIF || $this->tokens[$i][0] === T_FOR 
                || $this->tokens[$i][0] === T_FOREACH || $this->tokens[$i][0] === T_WHILE) && $this->tokens[$i+1] === '(' )
                {        
                    // skip condition in ( ) #这个while主要是跳过条件判断(),继续扫描后面的token,对token数组并不做处理
                    $f=2;
                    $braceopen = 1;
                    while($braceopen !== 0 ) 
                    {
                        if($this->tokens[$i+$f] === '(')
                            $braceopen++;
                        else if($this->tokens[$i+$f] === ')')
                            $braceopen--;
                        $f++;

                        if(!isset($this->tokens[$i+$f]))
                        {
                            addError('Could not find closing parenthesis of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);
                            break;    
                        }
                    }    
                    
                    // alternate syntax while(): endwhile; #这个if条件主要是为了给php的替代语法结构加上左右花括号,对于每一种条件或者循环关键字,都对应了相应的
                    结束标记,因此结合一个c变量,通过其自增来找到相应关键字的闭合token,然后再将:和endif都放到花括号内
                    if($this->tokens[$i+$f] === ':')
                    {
                        switch($this->tokens[$i][0])
                        {
                            case T_IF:
                            case T_ELSEIF: $endtoken = T_ENDIF; break;
                            case T_FOR: $endtoken = T_ENDFOR; break;
                            case T_FOREACH: $endtoken = T_ENDFOREACH; break;
                            case T_WHILE: $endtoken = T_ENDWHILE; break;
                            default: $endtoken = ';';
                        }
                    
                        $c=1;
                        while( $this->tokens[$i+$f+$c][0] !== $endtoken)
                        {
                            $c++;
                            if(!isset($this->tokens[$i+$f+$c]))
                            {
                                addError('Could not find end'.$this->tokens[$i][1].'; of alternate '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $f+1), $this->tokens[$i][2], $this->filename);
                                break;    
                            }
                        }
                        $this->wrapbraces($i+$f+1, $c+1, $i+$f+$c+2);
                    }
                    #这个if条件主要是为了针对 if() echo "1";只有一条指令,将其放到花括号内
                    // if body not in { (and not a do ... while();) wrap next instruction in braces
                    else if($this->tokens[$i+$f] !== '{' && $this->tokens[$i+$f] !== ';')
                    {
                        $c=1;
                        while($this->tokens[$i+$f+$c] !== ';' && $c<$max)
                        {
                            $c++;
                        }
                        $this->wrapbraces($i+$f, $c+1, $i+$f+$c+1);
                    }
                } 
              #为else if 添加花括号
            // rebuild else without { }    
                else if( $this->tokens[$i][0] === T_ELSE 
                && $this->tokens[$i+1][0] !== T_IF
                && $this->tokens[$i+1] !== '{')
                {    
                    $f=2;
                    while( $this->tokens[$i+$f] !== ';' && $f<$max)
                    {        
                        $f++;
                    }
                    $this->wrapbraces($i+1, $f, $i+$f+1);
                }
                 
            // rebuild switch (): endswitch;   
                #switch语句的处理,和if while等差不多,先扫描跳过判断语句     
                else if( $this->tokens[$i][0] === T_SWITCH && $this->tokens[$i+1] === '(')
                {
                    $newbraceopen = 1;
                    $c=2;
                    while( $newbraceopen !== 0 )
                    {
                        // watch function calls in function call
                        if( $this->tokens[$i + $c] === '(' )
                        {
                            $newbraceopen++;
                        }
                        else if( $this->tokens[$i + $c] === ')' )
                        {
                            $newbraceopen--;
                        }                    
                        else if(!isset($this->tokens[$i+$c]) || $this->tokens[$i + $c] === ';')
                        {
                            addError('Could not find closing parenthesis of switch-statement.', array_slice($this->tokens, $i, 10), $this->tokens[$i][2], $this->filename);
                            break;    
                        }
                        $c++;
                    }
                    #此时达到switch的方法体,因为switch一般来说是带花括号的,但对于endswitch的情况需要特殊处理一下,变为花括号形式
                    // switch(): ... endswitch;
                    if($this->tokens[$i + $c] === ':')
                    {
                        $f=1;
                        while( $this->tokens[$i+$c+$f][0] !== T_ENDSWITCH) #这里是通过f来找endswitch,c找:
                        {
                            $f++;
                            if(!isset($this->tokens[$i+$c+$f]))
                            {
                                addError('Could not find endswitch; of alternate switch-statement.', array_slice($this->tokens, $i, $c+1), $this->tokens[$i][2], $this->filename);
                                break;    
                            }
                        }
                        $this->wrapbraces($i+$c+1, $f+1, $i+$c+$f+2);
                    }
                }
            // rebuild switch case: without { }    
               #switch处理完了,此时处理switch内部的case
               这一部分主要是将每一条case后面的全部补全花括号
                else if( $this->tokens[$i][0] === T_CASE )
                {
                    $e=1;
                    while($this->tokens[$i+$e] !== ':' && $this->tokens[$i+$e] !== ';') #找到分号
                    {
                        $e++;
                        
                        if(!isset($this->tokens[$i+$e]))
                        {
                            addError('Could not find : or ; after '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);
                            break;    
                        }
                    }
                    $f=$e+1;
                    if(($this->tokens[$i+$e] === ':' || $this->tokens[$i+$e] === ';')
                    && $this->tokens[$i+$f] !== '{' 
                    && $this->tokens[$i+$f][0] !== T_CASE && $this->tokens[$i+$f][0] !== T_DEFAULT)
                    {
                        $newbraceopen = 0;
                        while($newbraceopen || (isset($this->tokens[$i+$f]) && $this->tokens[$i+$f] !== '}' 
                        && !(is_array($this->tokens[$i+$f]) 
                        && ($this->tokens[$i+$f][0] === T_BREAK || $this->tokens[$i+$f][0] === T_CASE 
                        || $this->tokens[$i+$f][0] === T_DEFAULT || $this->tokens[$i+$f][0] === T_ENDSWITCH) ) ))
                        {        
                            if($this->tokens[$i+$f] === '{')
                                $newbraceopen++;
                            else if($this->tokens[$i+$f] === '}')    
                                $newbraceopen--;
                            $f++;
                            
                            if(!isset($this->tokens[$i+$f]))
                            {
                                addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, $e+5), $this->tokens[$i][2], $this->filename);
                                break;    
                            }
                        }
                        if($this->tokens[$i+$f][0] === T_BREAK)
                        {
                            if($this->tokens[$i+$f+1] === ';')
                                $this->wrapbraces($i+$e+1, $f-$e+1, $i+$f+2);
                            // break 3;    
                            else
                                $this->wrapbraces($i+$e+1, $f-$e+2, $i+$f+3);
                        }    
                        else
                        {   # 无break的情况
                            $this->wrapbraces($i+$e+1, $f-$e-1, $i+$f);
                        }    
                        $i++;
                    }
                }
            // rebuild switch default: without { }  
                #针对default的情况,如果没有花括号,则添加花括号  
                else if( $this->tokens[$i][0] === T_DEFAULT
                && $this->tokens[$i+2] !== '{' )
                {
                    $f=2;
                    $newbraceopen = 0;
                    while( $this->tokens[$i+$f] !== ';' && $this->tokens[$i+$f] !== '}' || $newbraceopen )
                    {        
                        if($this->tokens[$i+$f] === '{')
                            $newbraceopen++;
                        else if($this->tokens[$i+$f] === '}')    
                            $newbraceopen--;
                        $f++;
                        
                        if(!isset($this->tokens[$i+$f]))
                        {
                            addError('Could not find ending of '.$this->tokens[$i][1].'-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);
                            break;    
                        }
                    }
                    $this->wrapbraces($i+2, $f-1, $i+$f+1);
                }
                #函数名小写
            // lowercase all function names because PHP doesn't care    
                else if( $this->tokens[$i][0] === T_FUNCTION )
                {
                    $this->tokens[$i+1][1] = strtolower($this->tokens[$i+1][1]);
                } 
                #函数调用小写   
                else if( $this->tokens[$i][0] === T_STRING && $this->tokens[$i+1] === '(')
                {
                    $this->tokens[$i][1] = strtolower($this->tokens[$i][1]);
                }    
            // switch a do while with a while (the difference in loop rounds doesnt matter
            // and we need the condition to be parsed before the loop tokens)
                else if( $this->tokens[$i][0] === T_DO )
                {
                    $f=2;
                    $otherDOs = 0;
                    // f = T_WHILE token position relative to i
                   #此时去找到对应该DO的while的token
                    while( $this->tokens[$i+$f][0] !== T_WHILE || $otherDOs )
                    {        
                        #忽略内层的DO while体
                        if($this->tokens[$i+$f][0] === T_DO)
                            $otherDOs++;
                        else if($this->tokens[$i+$f][0] === T_WHILE)
                            $otherDOs--;
                        $f++; #用f来标志找到的while位置
                        
                        if(!isset($this->tokens[$i+$f]))
                        {
                            addError('Could not find WHILE of DO-WHILE-statement.', array_slice($this->tokens, $i, 5), $this->tokens[$i][2], $this->filename);
                            break;    
                        }
                    }
                    
                    // rebuild do while without {} (should never happen but we want to be sure)
                    
                    #对于do后不带花括号的情况,带上花括号
                     if($this->tokens[$i+1] !== '{')
                    {
                        $this->wrapbraces($i+1, $f-1, $i+$f);
                        // by adding braces we added two new tokens
                        $f+=2; #因为在最外层的while前加了两个花括号占位,因此f+2才代表while的位置
                    }
                    #d代表while后的分号位置,这样不改变f所指的位置方便后面替换结构
                    $d=1;
                    // d = END of T_WHILE condition relative to i
                    while( $this->tokens[$i+$f+$d] !== ';' && $d<$max )
                    {
                        $d++;
                    }
                    #对do while语句进行重构,变成while结构
                    // reorder tokens and replace DO WHILE with WHILE
                    $this->tokens = array_merge(
                        array_slice($this->tokens, 0, $i), // before DO  
                        array_slice($this->tokens, $i+$f, $d), // WHILE condition f指向while d指向while结束
                        array_slice($this->tokens, $i+1, $f-1), // DO WHILE loop tokens i指向do循环体,f-1即内容结束
                        array_slice($this->tokens, $i+$f+$d+1, count($this->tokens)) // rest of tokens without while condition  while之后的token数组
                    );    
                }
            }    
        }
        // return tokens with rearranged key index
        $this->tokens = array_values($this->tokens);
    }

上面函数名小写要注意一点,php是弱类型语言,这里其本身不支持函数重载,即没有java的类的多态特性,但是同为解释型语言的python是支持函数重载的

第四部分:

token解析的最后一部分为:

function fix_ternary()
    {
        for($i=0,$max=count($this->tokens); $i<$max; $i++)
        {
            if( $this->tokens[$i] === '?' )
            {
                unset($this->tokens[$i]);
                // condition in brackets: fine, delete condition
                if($this->tokens[$i-1] === ')')
                {   #先找到)右括号,然后减f一直找到左括号,一直unset到左括号(
                    unset($this->tokens[$i-1]);
                    // delete tokens till ( 
                    $newbraceopen = 1;
                    $f = 2;
                    while( $newbraceopen !== 0 && $this->tokens[$i - $f] !== ';')
                    {
                        if( $this->tokens[$i - $f] === '(' )
                        {
                            $newbraceopen--;
                        }
                        else if( $this->tokens[$i - $f] === ')' )
                        {
                            $newbraceopen++;
                        }
                        unset($this->tokens[$i - $f]);    
                        $f++;
                        
                        if(($i-$f)<0)
                        {
                            addError('Could not find opening parenthesis in ternary operator (1).', array_slice($this->tokens, $i-5, 10), $this->tokens[$i+1][2], $this->filename);
                            break;    
                        }
                    }
                    #判断左括号左边是否是!或是自定义函数调用或者是isset、empty函数调用
                    //delete token before, if T_STRING
                    if($this->tokens[$i-$f] === '!' || (is_array($this->tokens[$i-$f]) 
                    && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET)))
                    {
                        unset($this->tokens[$i-$f]);
                    }
                    
                }
                // condition is a check or assignment
                #判断问号之前是不是逻辑比较,是的话肯定有操作数,需要unset掉
                else if(in_array($this->tokens[$i-2][0], Tokens::$T_ASSIGNMENT) || in_array($this->tokens[$i-2][0], Tokens::$T_OPERATOR) )
                {
                    // remove both operands
                    unset($this->tokens[$i-1]); #右操作数删除
                    unset($this->tokens[$i-2]); #删除运算符
                    // if operand is in braces
                    if($this->tokens[$i-3] === ')')  
                   #判断左边是否是函数调用,跟上面unset过程差不多,理想情况下是a()==1,但是对于1==a()没有考虑进去,因此对于这种unset并不能完全消除token,就直接走上面第一种a()这种形式的token解析
                    {
                        // delete tokens till ( 
                        $newbraceopen = 1;
                        $f = 4;
                        while( $newbraceopen !== 0 )
                        {
                            if( $this->tokens[$i - $f] === '(' )
                            {
                                $newbraceopen--;
                            }
                            else if( $this->tokens[$i - $f] === ')' )
                            {
                                $newbraceopen++;
                            }
                            unset($this->tokens[$i - $f]);    
                            $f++;
                            
                            if(($i-$f)<0 || $this->tokens[$i - $f] === ';')
                            {
                                addError('Could not find opening parenthesis in ternary operator (2).', array_slice($this->tokens, $i-8, 6), $this->tokens[$i+1][2], $this->filename);
                                break;    
                            }
                        }
                        #删除函数调用
                        //delete token before, if T_STRING
                        if(is_array($this->tokens[$i-$f]) 
                        && ($this->tokens[$i-$f][0] === T_STRING || $this->tokens[$i-$f][0] === T_EMPTY || $this->tokens[$i-$f][0] === T_ISSET))
                        {
                            unset($this->tokens[$i-$f]);
                        }
                    }

                    unset($this->tokens[$i-3]);
                    
                }
                // condition is a single variable, delete
                #对于单变量  $a? unset掉
                else if(is_array($this->tokens[$i-1]) && $this->tokens[$i-1][0] === T_VARIABLE)
                {
                    unset($this->tokens[$i-1]);
                }
            }    
        }
        // return tokens with rearranged key index
        $this->tokens = array_values($this->toknes);    
    }

这一部分主要就是对于三元操作符删除掉?前面的判断条件,此时只保留?后面的两种取值情况

参考:

https://xz.aliyun.com/t/2605#toc-6  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/614493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ICode国际青少年编程竞赛- Python-4级训练场-列表综合练习

ICode国际青少年编程竞赛- Python-4级训练场-列表综合练习 1、 Flyer[3].step(1) Flyer[7].step(2) Flyer[11].step(1) for i in range(4):Flyer[i * 2].step(1) Flyer[8].step(3)for i in range(3):Dev.turnRight()Dev.step(-5)2、 for i in range(5):Flyer[i5].step(Flyer[…

git 推送github 选https遇到登录 openSSH问题

使用https需要使用github令牌token作为密码&#xff0c; 使用SSH不需要登录。 还有一个问题&#xff1a; 创建github仓库后没有quick setup页面解决办法 千万不要点击任何多的操作&#xff01;&#xff01;&#xff01;输入仓库名&#xff0c;直接create&#xff01;&#x…

数据分析——业务指标分析

业务指标分析 前言一、业务指标分析的定义二、业务问题构建问题构建的要求 三、业务问题的识别在识别问题的阶段对于企业内部收益者的补充 四、竞争者分析竞争者分析的内容竞争者分析目的案例 五、市场机会识别好的市场机会必须满足的条件市场机会案例 六、风险控制数据分析师常…

多模态CLIP和BLIP

一、CLIP 全称为Contrastive Language-Image Pre-Training用于做图-文匹配&#xff0c;部署在预训练阶段&#xff0c;最终理解为图像分类器。 1.背景 以前进行分类模型时&#xff0c;存在类别固定和训练时要进行标注。因此面对这两个问题提出CLIP&#xff0c;通过这个预训练…

1.前端环境搭建

1.安装nodejs 因为我们开发Vue项目需要使用npm命令来创建和启动&#xff0c;安装node.js是为了获得这个命令&#xff0c;目前和使用node.js无关 下载地址&#xff1a;http://nodejs.cn/download/ 下载完之后安装&#xff0c;通过cmd查看是否安装成功 node --version2.创建项目…

老板必读:防数据泄露,保卫您的商业秘密

在信息技术高速发展的今天&#xff0c;数据泄露已成为所有企业都必须正视的风险。对于企业而言&#xff0c;数据不仅仅是一堆数字和信息的集合&#xff0c;更是企业的核心竞争力与商业秘密的载体。一旦数据泄露&#xff0c;不仅会导致经济损失&#xff0c;还可能使企业信誉受损…

如何解读 Web 自动化测试 Selenium API?

Web自动化测试是一种通过编写代码来模拟用户操作&#xff0c;并验证Web应用程序的功能和性能的技术。Selenium是一个流行的Web自动化测试工具&#xff0c;它提供了一组API来与Web浏览器进行交互。在本文中&#xff0c;我们将深入探讨Selenium API&#xff0c;并解释如何从零开始…

conan2 基础入门(01)-介绍

conan2 基础入门(01)-介绍 文章目录 conan2 基础入门(01)-介绍⭐什么是conan官网Why use Conan? ⭐使用现状版本情况个人知名开源企业 ⭐ConanCenter包中心github ⭐说明文档END ⭐什么是conan 官网 官网&#xff1a;Conan 2.0: C and C Open Source Package Manager 一句话来…

二维视觉尺寸测量简单流程

代码示例&#xff1a;opencv实战---物体尺寸测量_opencv尺寸测量精度-CSDN博客 灰度化 简化图像处理&#xff1a;灰度图像只包含亮度信息&#xff0c;不包含颜色信息&#xff0c;因此数据量比彩色图像小&#xff0c;处理起来更加简单和快速。这对于需要实时处理大量图像数据的场…

virtualbox下ubantu20.04版本实现与window的复制粘贴

1.建议开启双向 2.打开Ubuntu命令终端 快捷键 ctrialtt&#xff0c;具体在设置里面查看快捷键 3.卸载已有工具 sudo apt-get autoremove open-vm-tools4.安装 sudo apt-get install open-vm-tools-desktop5.记得sudo reboot重启 sudo reboot这里记得加上sudo&#xff0c;…

python软件测试Jmeter性能测试JDBC Request(结合数据库)的使用详解

这篇文章主要介绍了python软件测试Jmeter性能测试JDBC Request(结合数据库)的使用详解,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 JDBC Request 这个 Sampler 可以向数据…

大模型时代,程序员如何卷?

最近在看电影《碟中谍7》&#xff0c;该片讲述了特工伊森亨特尝试与一个被称为智体的全能人工智能作战&#xff0c;其可以即时访问任何在线网络&#xff0c;他和他的团队成员试图找回控制人工智能智体所必需的两部分钥匙并将其摧毁的故事。 在剧中&#xff0c;智体是一个虚拟反…

Java --- 集合(2)--- 这篇文章让你学会如何使用List集合

本期文章来自黑马程序员以及Java入门到精通&#xff0c;希望各位大佬发现文章的瑕疵及时表出&#xff0c;另外也感谢您的收看。话不多说&#xff0c;直接进入正题...... 目录 一.List集合的使用&#xff1a; 二.三种遍历List方式&#xff1a; 首先还是给大家呈现这幅图&#x…

Coursera吴恩达深度学习专项课程01: Neural Networks and Deep Learning 学习笔记 Week 03

Neural Networks and Deep Learning Course Certificate 本文是学习 https://www.coursera.org/learn/neural-networks-deep-learning 这门课的笔记 Course Intro 文章目录 Neural Networks and Deep LearningWeek 03: Shallow Neural NetworksLearning Objectives Neural Ne…

短剧APP开发,为短剧市场提供更多活力

近年来&#xff0c;短剧一直是一个大热赛道&#xff0c;不仅各大视频平台刮起了一股短剧热潮&#xff0c;各大品牌也纷纷开始进军短剧市场。短剧作为当下的流量密码&#xff0c;深受各大短剧观众与创业者的关注。吸引了大量的资本、制作方涌入到市场中&#xff0c;短剧行业发展…

taro3兼容支付宝/微信小程序的自定义拖拽排序组件

描述&#xff1a;列表可以完成拖拽排序 此组件是根据支付宝原生文档改编成taro-vue3的形式&#xff0c;只保留了拖拽的部分&#xff0c;其他功能都去除了&#xff0c;测试下来可以兼容支付宝和微信小程序。 支付宝原生文档&#xff1a; https://opendocs.alipay.com/support/…

未来办公新方式--智能体与程序完美配合

Agent AI智能体的未来 工作中&#xff0c;有时候我们就像是在不停地踩着缝纫机&#xff0c;重复地做着那些单调乏味的任务&#xff0c;不仅耗时费力&#xff0c;还特别容易出错。可是&#xff0c;咱们现在可是生活在数字化时代啊&#xff01;这时候&#xff0c;Python编程语言…

基于Nios软件实现流水灯+串口输出

基于NIOS-II软核实现流水灯串口输出 引言&#xff1a; ​ 在现代电子设计领域&#xff0c;FPGA&#xff08;现场可编程门阵列&#xff09;因其灵活性和并行处理能力而成为实现复杂数字系统的首选平台。Nios II&#xff0c;作为Altera&#xff08;现为Intel旗下&#xff09;提供…

综合能力 | 误差 | 学习笔记

误差指真值与观测值的差值。 误差分为系统误差&#xff08;消除方法&#xff1a;观测方法、仪器校正、修正等&#xff09;、偶然误差&#xff08;符合正态分布规律&#xff0c;进行计算纠正&#xff09;、粗差&#xff08;一般舍弃&#xff09;。 中误差&#xff08;均方差或标…

Linux进程——进程地址空间

前言&#xff1a;在讲完环境变量后&#xff0c;相信大家对Linux有更进一步的认识&#xff0c;而Linux进程概念到这也快接近尾声了&#xff0c;现在我们了解Linux进程中的地址空间&#xff01; 本篇主要内容&#xff1a; 了解程序地址空间 理解进程地址空间 探究页表和虚拟地址空…