静态分析-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 tokensfor($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 handlingelse 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 arraysif( 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 tokenif( 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 tokenelse 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 braceselse 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 callif( $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的tokenwhile( $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 iwhile( $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 conditionif($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_STRINGif($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 operandsunset($this->tokens[$i-1]); #右操作数删除unset($this->tokens[$i-2]); #删除运算符// if operand is in bracesif($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_STRINGif(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  

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么把手机ip地址变成了外省

在日常使用中&#xff0c;有时我们可能因为某些原因需要快速切换手机的IP地址&#xff0c;特别是当需要从一个省份切换到另一个省份的IP时。这种需求可能来源于网络访问限制、地理位置相关服务的使用、或者网络安全等方面的考虑。那么&#xff0c;怎么把手机IP地址变成外省呢&a…

测评工作室的养号成本,效率,纯净度,便捷性等问题怎么解决?

大家好&#xff0c;我是南哥聊跨境&#xff0c;最近有很多做测评工作室的朋友找到南哥&#xff0c;问我有什么新的测评养号系统可以解决成本&#xff0c;效率&#xff0c;纯净度&#xff0c;便捷性等问题 测评养号系统从最早的模拟器、虚拟机到911、VPS、手机设备等&#xff0…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-12-蜂鸣器

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

【大模型】LLaMA-1 模型介绍

文章目录 一、背景介绍二、模型介绍2.1 模型结构2.2 模型超参数2.3 SwiGLU 三、代码分析3.1 模型结构代码3.2 FairScale库介绍 四、LLaMA家族模型4.1 Alpaca4.2 Vicuna4.3 Koala(考拉)4.4 Baize (白泽)4.5 Luotuo (骆驼&#xff0c;Chinese)4.6 其他 参考资料 LLaMA&#xff08…

Human β-NGF ELISA试剂盒

走近β-NGF 神经生长因子(nerve growth factor, NGF)最初从小鼠颌下腺中以7S复合体的形式分离而得&#xff0c;复合体由三个非共价连接的亚基α,β和γ组成。 NGF的α和β亚基均属于丝氨酸蛋白酶组织激肽释放酶家族成员&#xff0c;β亚基也称为β-NGF或2…

国产银河麒麟V10SP1系统下搭建TiDB数据库操作步骤图文

开发目的&#xff1a;在国产银河麒麟系统中搭建TiDB数据库运行环境。 开发工具&#xff1a;银河麒麟系统V10SP1TiDBMySql数据库8.0。 具体步骤&#xff1a; 1、在VmWare虚拟机中安装好国产银河麒麟V10Sp1操作系统。 2、打开终端命令&#xff0c;安装TiDB相关软件&#xff1…

ALV Color-颜色

目录 前言 实战 列颜色 行颜色 单元格颜色 前言 在ABAP ALV中&#xff0c;Color颜色设置是一种增强列表显示效果的重要手段&#xff0c;可以用来突出显示特定行、列或单元格&#xff0c;以吸引用户注意或传达数据的特定状态。 颜色设置中有优先级顺序&#xff0c;他们是单元格…

线上剧本杀小程序:为行业带来新的活力,未来可期

剧本杀是一项新型的社交游戏活动&#xff0c;从前几年开始就呈现了快速发展态势&#xff0c;为大众带来沉浸式的游戏体验&#xff0c;一度成为年轻人娱乐休闲消费的首选方式&#xff0c;吸引了大量的消费者和商家。 不过&#xff0c;在市场发展中&#xff0c;剧本杀行业仍需要…

掌握文件重命名技巧:一次性处理多路径文件并赋予独立编号

在日常工作和生活中&#xff0c;我们经常需要处理大量的文件&#xff0c;而文件重命名则是一项非常常见的任务。如何高效地一次性处理多路径文件并赋予独立编号&#xff0c;成为许多用户关注的焦点。本文将介绍云炫文件管理器一些实用的文件重命名技巧&#xff0c;帮助您轻松应…

基于FPGA的去雾算法

去雾算法的原理是基于图像去模糊的原理&#xff0c;通过对图像中的散射光进行估计和去除来消除图像中的雾霾效果。 去雾算法通常分为以下几个步骤&#xff1a; 1. 导引滤波&#xff1a;首先使用导引滤波器对图像进行滤波&#xff0c;目的是估计图像中散射光的强度。导引滤波器…

《这就是ChatGPT》读书笔记

书名&#xff1a;这就是ChatGPT 作者&#xff1a;[美] 斯蒂芬沃尔弗拉姆&#xff08;Stephen Wolfram&#xff09; ChatGPT在做什么&#xff1f; ChatGPT可以生成类似于人类书写的文本&#xff0c;它基本任务是弄清楚如何针对它得到的任何文本产生“合理的延续”。当ChatGPT写…

Spring框架学习笔记(一):Spring基本介绍(包含IOC容器底层结构)

1 官方资料 1.1 官网 https://spring.io/ 1.2 进入 Spring5 下拉 projects, 进入 Spring Framework 进入 Spring5 的 github 1.3 在maven项目中导入依赖 <dependencies><!--加入spring开发的基本包--><dependency><groupId>org.springframework<…

STC -PWM

一.STC8H1K16初始化,以下一步配置后就会有波形输出. // // 函数: PWMB_Output_init // 描述: 用户初始化程序. // 参数: None. // 返回: None. // 版本: V1.0, 2020-09-28 //u16 PWM8__setDuty25000;u16 PWM8__setPeriod50000; void PWMB_Output_init(void) {PWMx_InitDefi…

数据驱动实战二

目标 掌握数据驱动的开发流程掌握如何读取JSON数据文件巩固PO模式 1. 案例 对TPshop网站的登录模块进行单元测试 1.1 实现步骤 编写测试用例采用PO模式的分层思想对页面进行封装编写测试脚本定义数据文件&#xff0c;实现参数化 1.2 用例设计 1.3 数据文件 {"login…

CSS-背景属性

目录 背景属性 background-color (背景颜色 ) background-image (背景图片 ) background-repeat (背景图平铺方式 ) no-repeat 不平铺 repeat-x 水平方向平铺 repeat-y 垂直方向平铺 repeat 平铺 background-position (背景图位置) background-size (背景缩…

【深耕 Python】Quantum Computing 量子计算机(4)量子物理概念(一)

写在前面 往期量子计算机博客&#xff1a; 【深耕 Python】Quantum Computing 量子计算机&#xff08;1&#xff09;图像绘制基础 【深耕 Python】Quantum Computing 量子计算机&#xff08;2&#xff09;绘制电子运动平面波 【深耕 Python】Quantum Computing 量子计算机&…

开源RAG框架汇总

前言 本文搜集了一些开源的基于LLM的RAG&#xff08;Retrieval-Augmented Generation&#xff09;框架&#xff0c;旨在吸纳业界最新的RAG应用方法与思路。如有错误或者意见可以提出&#xff0c;同时也欢迎大家把自己常用而这里未列出的框架贡献出来&#xff0c;感谢~ RAG应用…

Redis线程模型

文章目录 &#x1f496; Redis 单线程模型⭐ 单线程监听大量的客户端连接⭐ Redis 6.0 之前为什么不用多线程&#xff1f; &#x1f496; Redis多线程⭐ Redis 后台线程⭐ Redis 网络IO多线程 对于读写命令来说&#xff0c;Redis 一直是单线程模型。不过&#xff0c;在 Redis 4…

后缀树与后缀数组简介及代码模板 ← AcWing 2715

【题目来源】https://www.acwing.com/problem/content/2717/【题目描述】 给定一个长度为 n 的字符串&#xff0c;只包含大小写英文字母和数字。 将字符串中的 n 个字符的位置编号按顺序设为 1∼n。 并将该字符串的 n 个非空后缀用其起始字符在字符串中的位置编号表示。 现在要…

保姆级零基础微调大模型(LLaMa-Factory,多卡版)

此处非常感谢https://github.com/hiyouga/LLaMA-Factory这个项目。 看到网上的教程很多都是教如何用webui来微调的,这里出一期命令行多卡微调教程~ 1. 模型准备 模型下载比较方便的方法: 1. modelscope社区(首选,速度很高,并且很多需要申请的模型都有)注意要选择代码…