PCART  v1.4
Automated Repair of Python API Parameter Compatibility Issues
preprocess.py
Go to the documentation of this file.
1 
13 
14 
15 
16 import re
17 import ast
18 import shutil
19 from Path.getPath import *
20 from Extract.getCall import getCallFunction, modifyWithName
21 from Extract.extractCall import WithVisitor
22 from Tool.tool import getAst,getParameter,getLastAPIParameter,departAPI,departAPI2,ConditionalReturnTransformer, getFileName, getRunFile
23 from Tool.workspace import getRuntimePaths
24 
25 
26 
27 
31 def countBracket(s):
32  minL=0
33  minR=0
34  midL=0
35  midR=0
36  huaL=0
37  huaR=0
38  flag=1
39  cnt=0 #计算引号的个数
40  for it in s:
41  if it=='\'': #引号内的括号不计数
42  flag=0
43  cnt+=1
44  if cnt%2==0:
45  flag=1
46  elif flag==1:
47  if it=='(':
48  minL+=1
49  elif it==')':
50  minR+=1
51  elif it=='[':
52  midL+=1
53  elif it==']':
54  midR+=1
55  elif it=='{':
56  huaL+=1
57  elif it=='}':
58  huaR+=1
59  return minL,minR,midL,midR,huaL,huaR
60 
61 
62 
63 
66 def oneLine(filePath):
67  try:
68  root=getAst(filePath)
69  #重写回文件
70  with open(filePath,'w',encoding='UTF-8') as fw:
71  newCode=ast.unparse(root)
72  # newCode=re.sub(r'""".*?"""','pass',newCode,flags=re.DOTALL) #去掉代码中的注释
73  fw.write(f"{newCode}\n")
74  except Exception as e:
75  print(f"oneLine --> {filePath} parse to ast failed: {e}")
76 
77 
78 
79 
83  try:
84  root=getAst(filePath)
85  transformer = ConditionalReturnTransformer()
86  new_root = transformer.visit(root)
87  #重写回文件
88  with open(filePath,'w',encoding='UTF-8') as fw:
89  newCode=ast.unparse(root)
90  fw.write(f"{newCode}\n")
91  except Exception as e:
92  print(f"expandConditionalReturn --> {filePath} parse to ast failed: {e}")
93 
94 
95 
96 
105 def getListVar(root,ansLst):
106  for node in ast.iter_child_nodes(root):
107  if isinstance(node,ast.ListComp):
108  s=ast.unparse(node.generators).lstrip(' ')
109  pattern="for (.*?) in"
110  lst=re.findall(pattern,s)
111  if len(lst)==1:
112  temp=lst[0]
113  if temp[0]=='(':
114  var=temp[1:-1]
115  else:
116  var=temp
117  s1=f"{var}=[{temp} {s}][0]"
118  ansLst.append(s1)
119 
120  getListVar(node,ansLst)
121 
122 
123 
124 
132 def getDictVar(root,ansLst):
133  for node in ast.iter_child_nodes(root):
134  if isinstance(node,ast.DictComp):
135  s=ast.unparse(node.generators).lstrip(' ')
136  pattern="for (.*?) in"
137  lst=re.findall(pattern,s)
138  if len(lst)==1:
139  temp=lst[0]
140  if temp[0]=='(':
141  var=temp[1:-1]
142  else:
143  var=temp
144  s1=f"{var}=[{temp} {s}][0]"
145  ansLst.append(s1)
146 
147  getDictVar(node,ansLst)
148 
149 
150 
151 
156 def convertLocalVar(filePath,libName):
157  with open(filePath,'r',encoding='UTF-8') as fr:
158  codeLst=fr.readlines()
159 
160  for i in range(len(codeLst)):
161  s=codeLst[i].lstrip(' ').rstrip(' ')
162  try:
163  root=ast.parse(s,filename='<unknown>',mode='exec')
164  except Exception as e:
165  continue
166 
167  #step1:判断代码语句中是否含有列表推导式或字典推导式
168  listComp=0
169  dictComp=0
170  for node in ast.walk(root):
171  if isinstance(node,ast.ListComp):
172  listComp=1
173  if isinstance(node,ast.DictComp):
174  dictComp=1
175 
176  if not listComp and not dictComp:
177  continue
178 
179  #step2:判断列表推导式中是否含有第三方库调用的API
180  flag=0
181  _,callDict=getCallFunction(filePath,libName)
182  callLst=[record['call_text'] for record in callDict.values()]
183  flag=0
184  for node in ast.walk(root):
185  if isinstance(node, ast.Call):
186  callState=ast.unparse(node)
187  callState=callState.replace(' ','').replace('"','').replace("'",'')
188  for it in callLst:
189  if callState==it.replace(' ','').replace('"','').replace("'",''):
190  flag=1
191  break
192  if flag==1:
193  break
194  if flag==0:
195  continue
196 
197  #step3:提取出列表推导式中的变量
198  ansLst=[]
199  if listComp:
200  getListVar(root,ansLst)
201  spaceNum=countSpace(codeLst[i])
202  temp=''
203  for it in ansLst:
204  tryStr="try:\n"
205  exceptStr="except:\n"
206  passStr="pass\n"
207  if spaceNum:
208  tryStr=' '*spaceNum*1+tryStr
209  it=' '*spaceNum*1+' '*4+it+'\n'
210  exceptStr=' '*spaceNum*1+exceptStr
211  passStr=' '*spaceNum*1+' '*4+passStr
212  else:
213  spaceNum=4
214  it=' '*spaceNum*1+it+'\n'
215  passStr=' '*spaceNum*1+passStr
216  spaceNum=0 #用完之后就置为0
217 
218  s=tryStr+it+exceptStr+passStr
219  temp=temp+s
220  temp+=codeLst[i]
221  codeLst[i]=temp
222 
223  if dictComp:
224  getDictVar(root,ansLst)
225  spaceNum=countSpace(codeLst[i])
226  temp=''
227  for it in ansLst:
228  tryStr="try:\n"
229  exceptStr="except:\n"
230  passStr="pass\n"
231  if spaceNum:
232  tryStr=' '*spaceNum*1+tryStr
233  it=' '*spaceNum*1+' '*4+it+'\n'
234  exceptStr=' '*spaceNum*1+exceptStr
235  passStr=' '*spaceNum*1+' '*4+passStr
236  else:
237  spaceNum=4
238  it=' '*spaceNum*1+it+'\n'
239  passStr=' '*spaceNum*1+passStr
240  spaceNum=0 #用完之后就置为0
241 
242  s=tryStr+it+exceptStr+passStr
243  temp=temp+s
244  temp+=codeLst[i]
245  codeLst[i]=temp
246 
247 
248  with open(filePath,'w',encoding='UTF-8') as fw:
249  for it in codeLst:
250  fw.write(it)
251 
252 
253 
254 
259 def findAssignCall(root):
260  assignLst=[]
261  for node in ast.walk(root):
262  if isinstance(node,ast.Assign) and isinstance(node.value,ast.Call):
263  target=ast.unparse(node.targets)
264  assignLst.append(target)
265  return assignLst
266 
267 
268 
276 def getAssignReceiverExpr(root,source,receiverName,lineno):
277  aliasName=receiverName.split('.')[0]
278  receiverSuffix=receiverName[len(aliasName):]
279  expr=None
280  exprNode=None
281  bestLine=-1
282 
283  scopeBodies=[root.body]
284  for node in ast.walk(root):
285  if isinstance(node,(ast.FunctionDef,ast.AsyncFunctionDef,ast.ClassDef)):
286  start=node.lineno
287  end=getattr(node,'end_lineno',start)
288  if start<lineno<=end:
289  scopeBodies.append(node.body)
290 
291  for body in scopeBodies:
292  for stmt in body:
293  if not hasattr(stmt,'lineno') or stmt.lineno>=lineno:
294  continue
295  if isinstance(stmt,(ast.If,ast.For,ast.AsyncFor,ast.While,ast.Try,ast.With,ast.AsyncWith)):
296  continue
297  value=None
298  targets=[]
299  if isinstance(stmt,ast.Assign):
300  value=stmt.value
301  targets=stmt.targets
302  elif isinstance(stmt,ast.AnnAssign):
303  value=stmt.value
304  targets=[stmt.target]
305  if value is None:
306  continue
307  for target in targets:
308  if isinstance(target,ast.Name) and target.id==aliasName and stmt.lineno>bestLine:
309  sourceExpr=ast.get_source_segment(source,value)
310  if sourceExpr:
311  expr=sourceExpr.strip()
312  exprNode=value
313  bestLine=stmt.lineno
314 
315  if exprNode is None:
316  return None
317 
318  localNames=set()
319  for body in scopeBodies:
320  for stmt in body:
321  if not hasattr(stmt,'lineno') or stmt.lineno>=lineno:
322  continue
323  targets=[]
324  if isinstance(stmt,ast.Assign):
325  targets=stmt.targets
326  elif isinstance(stmt,(ast.AnnAssign,ast.AugAssign)):
327  targets=[stmt.target]
328  for target in targets:
329  for node in ast.walk(target):
330  if isinstance(node,ast.Name):
331  localNames.add(node.id)
332 
333  for node in ast.walk(root):
334  if isinstance(node,(ast.FunctionDef,ast.AsyncFunctionDef)):
335  start=node.lineno
336  end=getattr(node,'end_lineno',start)
337  if start<lineno<=end:
338  for arg in node.args.posonlyargs+node.args.args+node.args.kwonlyargs:
339  localNames.add(arg.arg)
340  if node.args.vararg:
341  localNames.add(node.args.vararg.arg)
342  if node.args.kwarg:
343  localNames.add(node.args.kwarg.arg)
344 
345  loadedNames={node.id for node in ast.walk(exprNode) if isinstance(node,ast.Name) and isinstance(node.ctx,ast.Load)}
346  if loadedNames & localNames:
347  return None
348 
349  if receiverSuffix:
350  return f'({expr}){receiverSuffix}'
351  return expr
352 
353 
354 
361 def getSelfReceiverExpr(root,lineno,methodName):
362  for node in ast.walk(root):
363  if not isinstance(node,ast.ClassDef):
364  continue
365  start=node.lineno
366  end=getattr(node,'end_lineno',start)
367  if not (start<lineno<=end) or len(node.bases)==0:
368  continue
369  defNames=set()
370  for item in ast.iter_child_nodes(node):
371  if isinstance(item,(ast.FunctionDef,ast.AsyncFunctionDef)):
372  defNames.add(item.name)
373  if methodName in defNames:
374  return None
375  return ast.unparse(node.bases[0])
376  return None
377 
378 
379 
380 
385 def countSpace(s):
386  cntSpace=0
387  for it in s:
388  if it==' ':
389  cntSpace+=1
390  else:
391  break
392  return cntSpace
393 
394 
395 
396 
404 def getImportLine(codeLst):
405  #首先判断from import语句中是否含有特殊的__future__
406  index=-1
407  for i in range(len(codeLst)):
408  if 'import' in codeLst[i] and '__future__' in codeLst[i]:
409  index=i
410 
411  #若没有future,再判断开头是否存在'"""'注释
412  count=0
413  if index==-1 and '"""' in codeLst[0]:
414  for i in range(0,len(codeLst)):
415  if '"""' in codeLst[i]:
416  index=i
417  count+=1
418  if count==2:
419  break
420 
421  index+=1
422 
423  return index
424 
425 
426 
427 
433  decoratorLst = []
434  for n in ast.walk(root):
435  if isinstance(n, (ast.FunctionDef, ast.ClassDef)) and n.decorator_list:
436  for decorator in n.decorator_list:
437  if isinstance(decorator, ast.Call): # and isinstance(decorator.func.value, ast.Name):
438  decoratorLst.append(ast.unparse(decorator.func))
439 
440  return decoratorLst
441 
442 
443 
453 def getDecoratorInsertIndex(codeLst,index):
454  insertIndex=index
455  if not codeLst[index].lstrip().startswith('@'):
456  return insertIndex
457 
458  spaceNum=countSpace(codeLst[index])
459  j=index-1
460  while j>=0:
461  if codeLst[j].lstrip().startswith('@') and countSpace(codeLst[j])==spaceNum:
462  insertIndex=j
463  j-=1
464  continue
465  break
466 
467  return insertIndex
468 
469 
470 
471 
477 def addDictSingle(callAPI,filePath,callKey):
478  with open(filePath,'r',encoding='UTF-8') as fr:
479  codeLst=fr.readlines()
480  source=''.join(codeLst)
481 
482  lineno=getImportLine(codeLst)
483  importDict='from recordValue import paraValueDict\nfrom recordValue import callsiteInfoDict\n'
484  codeLst.insert(lineno,importDict)
485 
486  paraStr=getLastAPIParameter(callAPI) #获取最后一个API的参数
487  parameterLst=getParameter(paraStr,space=0) #项目参数不去空格
488  root=getAst(filePath)
489  targetLst=findAssignCall(root)
490 
491  #找出树中所有withitem call节点 -- 2025/5/19
492  withitem_visitor = WithVisitor()
493  withitem_visitor.visit(root)
494  withitem_call_names = withitem_visitor.get_withitem_call() #dict
495 
496  for i in range(len(codeLst)): #每次只会往列表中插入一个元素
497  if callAPI.replace(' ','') in codeLst[i].replace(' ','') and 'def ' not in codeLst[i] and 'paraValueDict' not in codeLst[i] and 'callsiteInfoDict' not in codeLst[i]:
498  spaceNum=countSpace(codeLst[i])
499  dicString1=''
500  l=departAPI(callAPI)
501  l2=departAPI2(callAPI)
502  firstPart=''
503  for it in l2:
504  if '(' not in it:
505  firstPart+=it+'.'
506  firstPart=firstPart.rstrip('.')
507 
508  callsiteInfo={
509  'call_text': callAPI,
510  'format_api': callAPI,
511  }
512  displayCall=callAPI.replace('\n',' ')
513  dicStringComment=f'# PCART callsite: {displayCall}\n'
514  dicStringKey=f'__pcart_runtime_callsite_key__={repr(callKey)}\n'
515  dicString0=f'callsiteInfoDict[__pcart_runtime_callsite_key__]={repr(callsiteInfo)}\n'
516  if firstPart and (firstPart.split('.')[0] in targetLst or firstPart.split('.')[0]=='self') and len(l)==1:
517  lineNo = i + 1
518  receiverExpr=None
519  if firstPart.split('.')[0] in targetLst:
520  receiverExpr=getAssignReceiverExpr(root,source,firstPart,lineNo)
521  elif firstPart.split('.')[0]=='self':
522  methodName=callAPI.split('(')[0].split('.')[-1]
523  receiverExpr=getSelfReceiverExpr(root,lineNo,methodName)
524  if receiverExpr:
525  receiverExpr=receiverExpr.replace('\\','\\\\').replace('"','\\"')
526  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={{}}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"object\"]={firstPart}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"expr\"]=\"{receiverExpr}\"\n'
527  else:
528  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={firstPart}\n'
529  elif len(l)>1: #df.a(x).b(y), np.max(...), torch.nn.Sequential(...)
530  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={l[-2]}\n'
531 
532  #判断API是否为withitem中的别名调用 -- 2025/5/19
533  if firstPart and firstPart.split('.')[0] in withitem_call_names:
534  lineNo = i + 1
535  initialCallName = modifyWithName(firstPart, withitem_call_names, lineNo).rstrip('.')
536  # withitem接收者同时保存运行时对象和还原表达式,动态阶段按可用候选依次尝试
537  initialCallName=initialCallName.replace('\\','\\\\').replace('"','\\"')
538  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={{}}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"object\"]={firstPart}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"expr\"]=\"{initialCallName}\"\n'
539 
540  #再保存API的参数值
541  dicString2='paraValueDict[__pcart_runtime_callsite_key__]=['
542  for para in parameterLst: #
543  if '=' in para and "'='" not in para and '"="' not in para: #若参数的形式为key=f(x=1),只要确保=的前面不含括号即可
544  pos=para.find('=') #找到第一个=的位置
545  if '(' not in para[0:pos] and "'" not in para[0:pos] and '"' not in para[0:pos] and para[pos+1]!='=': #等号前面也不能出现引号,比如f('x= ',y=1)
546  para=para[pos+1:]
547 
548  para=para.lstrip('*') #有的参数会带*,2023-12-20
549  dicString2=dicString2+para+','
550  dicString2=dicString2.rstrip(',')+']\n'
551 
552  while spaceNum>0:
553  dicStringComment=' '+dicStringComment
554  dicStringKey=' '+dicStringKey
555  dicString0=' '+dicString0
556  if dicString1:
557  dicString1=' '+dicString1
558  dicString2=' '+dicString2
559  spaceNum-=1
560 
561  #插入的时候要考虑是否含有elif,如果有elif要把它插在elif后面
562  if 'elif' not in codeLst[i]:
563  insertIndex=getDecoratorInsertIndex(codeLst,i)
564  codeLst.insert(insertIndex,dicString2)
565  if dicString1:
566  codeLst.insert(insertIndex,dicString1)
567  codeLst.insert(insertIndex,dicString0)
568  codeLst.insert(insertIndex,dicStringKey)
569  codeLst.insert(insertIndex,dicStringComment)
570  else:
571  if dicString1:
572  dicString1=dicString1.lstrip(' ')#去掉之前添加的空格,重新计算开头的空格数
573  dicStringComment=dicStringComment.lstrip(' ')
574  dicStringKey=dicStringKey.lstrip(' ')
575  dicString0=dicString0.lstrip(' ')
576  dicString2=dicString2.lstrip(' ')
577  for j in range(i+1,len(codeLst)):
578  if codeLst[j]!='\n' and '#' not in codeLst[j]:
579  spaceNum=countSpace(codeLst[j])
580  while spaceNum>0:
581  dicStringComment=' '+dicStringComment
582  dicStringKey=' '+dicStringKey
583  dicString0=' '+dicString0
584  if dicString1:
585  dicString1=' '+dicString1
586  dicString2=' '+dicString2
587  spaceNum-=1
588 
589  codeLst.insert(j,dicString2)
590  if dicString1:
591  codeLst.insert(j,dicString1)
592  codeLst.insert(j,dicString0)
593  codeLst.insert(j,dicStringKey)
594  codeLst.insert(j,dicStringComment)
595  break
596 
597  break
598 
599 
600  if codeLst[0]=='pass\n':
601  codeLst=codeLst[1:]
602  with open(filePath,'w',encoding='UTF-8') as fw:
603  for it in codeLst:
604  fw.write(it)
605 
606 
607 
608 
620 def addDictAll(projPath,projName,filePath,copyRoot,runFileLst,libName,runPath,runCommand,pcresolveLookup=None):
621  with open(filePath,'r',encoding='UTF-8') as fr:
622  code=fr.read()
623  fr.seek(0) #将文件指针重新定位到文件的开头
624  codeLst=fr.readlines()
625  try:
626  root=ast.parse(code,filename='<unknown>',mode='exec')
627  except Exception as e:
628  print(f"addDictAll --> ast.parse failed, {filePath}: {e}")
629  return
630 
631  #处理相关路径
632  fileName = os.path.basename(filePath)[0:-3]
633  fileRelativePath=os.path.relpath(filePath,os.path.join(copyRoot,projName))
634  fileAbsolutePath=os.path.join(projPath,fileRelativePath)
635 
636  lineno=getImportLine(codeLst)
637  importDict='from recordValue import paraValueDict\nfrom recordValue import apiCoveredSet\nfrom recordValue import callsiteInfoDict\n'
638  codeLst.insert(lineno,importDict)
639  if pcresolveLookup is None:
640  _,callDict=getCallFunction(fileAbsolutePath,libName,projPath)
641  else:
642  _,callDict=getCallFunction(fileAbsolutePath,libName,projPath,pcresolveLookup=pcresolveLookup)
643 
644  targetLst=findAssignCall(root) #用来区分调用者是否来自赋值语句,比如a.f(), tf.f(), or self.f()
645 
646  #找出树中所有withitem call节点 -- 2025/5/19
647  try:
648  withitem_root = getAst(fileAbsolutePath)
649  except Exception:
650  withitem_root = root
651  withitem_visitor = WithVisitor()
652  withitem_visitor.visit(withitem_root)
653  withitem_call_names = withitem_visitor.get_withitem_call() #dict
654 
655  insertStartLine=0 #记录每次插桩的行
656  preInsertAPI='' #记录上一个插桩的API是哪个
657  preInsertAPICount=0 #记录上一个插桩行中出现了几次被插的API
658  for artifactId,record in callDict.items(): #key是调用点artifact id,value是结构化调用点记录
659  flag=0 #标记API是否找到了插桩的位置
660  lineno=int(record['lineno']) #这个lineno是原项目中的行数
661  callState=record['call_text']
662  paraStr=record['parameters']
663  callAPI=callState.replace(' ','')
664  if callAPI==preInsertAPI: #判断当前要处理的API与上一个API是否相同
665  preInsertAPICount-=1
666  if preInsertAPICount<=0:
667  insertStartLine+=1
668 
669  i=insertStartLine #从第i行开始向后找
670  while i<len(codeLst):
671  #API调用在i行代码中,且i行代码不是函数定义语句、插桩语句和运行覆盖检查语句
672  if callAPI in codeLst[i].replace(' ','') and 'def ' not in codeLst[i] and 'paraValueDict' not in codeLst[i] and 'apiCoveredSet' not in codeLst[i] and 'callsiteInfoDict' not in codeLst[i]:
673  if callAPI!=preInsertAPI:#只有当前API不等于上一个被插API时,才需要重新计算preAPICount
674  preInsertAPICount=codeLst[i].replace(' ','').count(callAPI)
675  preInsertAPI=callAPI
676  flag=1
677  spaceNum=countSpace(codeLst[i])
678  callSiteKey=artifactId
679  callsiteInfo={
680  'artifact_hash': record.get('artifact_hash',''),
681  'rel_path': record.get('rel_path',''),
682  'lineno': record.get('lineno'),
683  'col_offset': record.get('col_offset'),
684  'end_lineno': record.get('end_lineno'),
685  'end_col_offset': record.get('end_col_offset'),
686  'call_text': record.get('call_text',''),
687  'format_api': record.get('format_api',''),
688  }
689  displayPath=record.get('rel_path','')
690  displayLine=record.get('lineno')
691  displayColumn=record.get('col_offset')
692  displayCall=str(record.get('call_text','')).replace('\n',' ')
693  displayPrefix=f'{displayPath}:{displayLine}:{displayColumn} ' if displayPath else ''
694  dicStringComment=f'# PCART callsite: {displayPrefix}{displayCall}\n'
695  dicStringKey=f'__pcart_runtime_callsite_key__={repr(callSiteKey)}\n'
696  dicString0=f'callsiteInfoDict[__pcart_runtime_callsite_key__]={repr(callsiteInfo)}\n'
697  l=departAPI(callState)
698  l2=departAPI2(callState)
699  firstPart=''
700  for it in l2:
701  if '(' not in it:
702  firstPart+=it+'.'
703  firstPart=firstPart.rstrip('.')
704  dicString1=''
705 
706  #判断API是否具有上文依赖,比如self.f(x), a(x).b(y)中的a(x),或者a.f(x)中的a
707  #df.a(x).b(y)这种情况如何解决
708  #a.b.c(x)
709  # if '(' not in firstPart and (firstPart in targetLst or firstPart=='self'):
710  #self.f(x), a.f(x), a.b.c(x)
711  if firstPart and (firstPart.split('.')[0] in targetLst or firstPart.split('.')[0]=='self') and len(l)==1:
712  receiverExpr=None
713  if firstPart.split('.')[0] in targetLst:
714  receiverExpr=getAssignReceiverExpr(root,code,firstPart,lineno)
715  elif firstPart.split('.')[0]=='self':
716  methodName=callState.split('(')[0].split('.')[-1]
717  receiverExpr=getSelfReceiverExpr(root,lineno,methodName)
718  if receiverExpr:
719  receiverExpr=receiverExpr.replace('\\','\\\\').replace('"','\\"')
720  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={{}}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"object\"]={firstPart}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"expr\"]=\"{receiverExpr}\"\n'
721  else:
722  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={firstPart}\n'
723  elif len(l)>1: #df.a(x).b(y), np.max(...), torch.nn.Sequential(...)
724  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={l[-2]}\n'
725 
726  #判断API是否为withitem中的别名调用 -- 2025/5/19
727  if firstPart and firstPart.split('.')[0] in withitem_call_names:
728  initialCallName = modifyWithName(firstPart, withitem_call_names, lineno).rstrip('.')
729  initialCallName = initialCallName.rstrip('.')
730  # withitem接收者同时保存运行时对象和还原表达式,非withitem调用仍保持原有单值保存
731  initialCallName=initialCallName.replace('\\','\\\\').replace('"','\\"')
732  dicString1=f'paraValueDict[\"@\"+__pcart_runtime_callsite_key__]={{}}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"object\"]={firstPart}; paraValueDict[\"@\"+__pcart_runtime_callsite_key__][\"expr\"]=\"{initialCallName}\"\n'
733  #再保存API的参数值
734  dicString2='paraValueDict[__pcart_runtime_callsite_key__]=['
735  paraLst=getParameter(paraStr,space=0) #项目参数不去空格2023-12-14
736  for para in paraLst:
737  if '=' in para and "'='" not in para and '"="' not in para: #若参数的形式为key=f(x=1),只要确保=的前面不含括号即可
738  pos=para.find('=') #找到第一个=的位置,存在x=(a==b)和a==b形式
739  if '(' not in para[0:pos] and "'" not in para[0:pos] and '"' not in para[0:pos] and para[pos+1]!='=': #等号前面也不能出现引号,比如f('x= ',y=1)
740  para=para[pos+1:] #把参数的值保存下来
741 
742  para=para.lstrip('*') #有的参数会带*号,2023-12-20
743  dicString2=dicString2+para+','
744  dicString2=dicString2.rstrip(',')+']\n'
745 
746  dicString3='apiCoveredSet.add(__pcart_runtime_callsite_key__)\n'
747  while spaceNum>0:
748  dicStringComment=' '+dicStringComment
749  dicStringKey=' '+dicStringKey
750  dicString0=' '+dicString0
751  if dicString1:
752  dicString1=' '+dicString1
753  dicString2=' '+dicString2
754  dicString3=' '+dicString3
755  spaceNum-=1
756 
757  #插入的时候要考虑是否含有elif,如果有elif要把它插在elif后面
758  #不能将字典插入到if和elif之间,所以需要找到elif后的下一个非空行再插入
759  if 'elif' not in codeLst[i]:
760  insertIndex=getDecoratorInsertIndex(codeLst,i)
761  codeLst.insert(insertIndex,dicString3)
762  codeLst.insert(insertIndex,dicString2)
763  if dicString1:
764  codeLst.insert(insertIndex,dicString1)
765  codeLst.insert(insertIndex,dicString0)
766  codeLst.insert(insertIndex,dicStringKey)
767  codeLst.insert(insertIndex,dicStringComment)
768  #记录当前插在了哪一行
769  if dicString1:
770  insertStartLine=insertIndex+6
771  else:
772  insertStartLine=insertIndex+5
773  else:
774  if dicString1:
775  dicString1=dicString1.lstrip(' ') #去掉之前添加的空格,重新计算开头的空格数
776  dicStringComment=dicStringComment.lstrip(' ')
777  dicStringKey=dicStringKey.lstrip(' ')
778  dicString0=dicString0.lstrip(' ')
779  dicString2=dicString2.lstrip(' ')
780  dicString3=dicString3.lstrip(' ')
781  for j in range(i+1,len(codeLst)):
782  if codeLst[j]!='\n' and '#' not in codeLst[j]:
783  spaceNum=countSpace(codeLst[j])
784  while spaceNum>0:
785  dicStringComment=' '+dicStringComment
786  dicStringKey=' '+dicStringKey
787  dicString0=' '+dicString0
788  if dicString1:
789  dicString1=' '+dicString1
790  dicString2=' '+dicString2
791  dicString3=' '+dicString3
792  spaceNum-=1
793  codeLst.insert(j,dicString3)
794  codeLst.insert(j,dicString2)
795  if dicString1:
796  codeLst.insert(j,dicString1)
797  codeLst.insert(j,dicString0)
798  codeLst.insert(j,dicStringKey)
799  codeLst.insert(j,dicStringComment)
800  #记录当前插在了哪一行
801  if dicString1:
802  insertStartLine=j+6
803  else:
804  insertStartLine=j+5
805  break
806 
807  break
808 
809  i+=1
810 
811  if flag==0:
812  # Debug aid: log uninstrumentable callsites for diagnosis
813  # 调试辅助:记录无法插桩的调用点以便诊断
814  print(f"{fileName}#{lineno}-->{callState}\n")
815 
816  if codeLst[0]=='pass\n':
817  codeLst=codeLst[1:]
818  with open(filePath,'w',encoding='UTF-8') as fw:
819  for it in codeLst:
820  fw.write(it)
821 
822 
823 
824 
836 def handleRunFile(file,runPath,runCommand):
837  with open(file,'r',encoding='UTF-8') as fr:
838  codeLst=fr.readlines()
839  lineno=getImportLine(codeLst)
840  codeLst.insert(lineno,f"from recordValue import paraValueDict\n")
841 
842  if codeLst[0]=='pass\n':
843  codeLst=codeLst[1:]
844  with open(file,'w',encoding='UTF-8') as fw:
845  for it in codeLst:
846  fw.write(it)
847 
848 
852 def obtainDef(sourcePath):
853  with open(sourcePath,'r',encoding='UTF-8') as fr:
854  code=fr.read()
855  try:
856  root=ast.parse(code,filename='<unknown>',mode='exec') #将源码解析成AST语法树
857  except:
858  print(sourcePath)
859  return
860  fw=open('Copy/defFile.py','a',encoding='UTF-8')
861  for node in ast.iter_child_nodes(root):
862  if isinstance(node,ast.ClassDef) or isinstance(node,ast.FunctionDef):
863  s=ast.unparse(node)
864  fw.write(f"{s}\n")
865  fw.close()
866 
867 
868 
873 def modifyFromImport(filePath,importStatement):
874  # with open(filePath,'r') as fr:
875  with open(filePath, 'r', encoding='UTF-8') as fr:
876  codeLst=fr.readlines()
877 
878  s='\n'.join(importStatement)+'\n'
879  codeLst.insert(0,s)
880  # with open(filePath,'w') as fw:
881  with open(filePath, 'w', encoding='UTF-8') as fw:
882  for it in codeLst:
883  fw.write(it)
884 
885 
886 
887 
907 def saveConstantAssign(astNode, constantVar, nonConstantVar, decoratorLst):
908  flag = 0
909  astBody = []
910  for n in ast.walk(astNode):
911  if isinstance(n,ast.Assign):
912  value = n.value
913  valueAstContent = ast.dump(n.value)
914  targetAstContent = ast.dump(n.targets[0])
915  if isinstance(n.targets[0], ast.Name):
916  varName = n.targets[0].id
917  #如果赋值语句的变量名在装饰器列表中出现过,则保留该赋值语句 2025/5/13
918  if any(varName in decorator.split('.') for decorator in decoratorLst):
919  continue
920  #如果赋值语句的变量名是常量变量名,则保留该赋值语句 2025/5/31
921  if isinstance(value, ast.Constant):
922  if varName in nonConstantVar:
923  flag=1
924  break
925  else:
926  targets = n.targets
927  if targets:
928  target = targets[0]
929  if isinstance(target, ast.Name):
930  constantVar.append(target.id)
931  else:
932  # 使用正则表达式匹配单引号包围的内容
933  pattern = r"id='([^']*)'"
934  valueMatches = re.findall(pattern, valueAstContent)
935  targetMatches = re.findall(pattern, targetAstContent)
936  if not len(valueMatches):
937  if targetMatches[0] in nonConstantVar:
938  flag=1
939  break
940  else:
941  for match in valueMatches:
942  if match not in constantVar:
943  nonConstantVar.append(targetMatches[0])
944  flag=1
945  break
946  else:
947  flag=1
948  break
949  if flag==1:
950  return flag
951  else:
952  astBody.append(astNode)
953  return astBody
954 
955 
956 
957 
962 def saveStructure(projPath,libName):
963  pathObj=Path('DF')
964  pathObj.getPath(projPath)
965  filePath=[it for it in pathObj.path if it.endswith('py')]
966  for file in filePath:
967  _,callDict=getCallFunction(file,libName)
968  callLst=[record['call_text'] for record in callDict.values()]
969  # with open(file,'r') as fr:
970  with open(file, 'r', encoding='UTF-8') as fr:
971  code=fr.read()
972  try:
973  root=ast.parse(code,filename='<unknown>',mode='exec')
974  except Exception as e:
975  print(f"saveStructure --> ast parse failed in {file}: {e}")
976  continue
977  newBody=[]
978  constantVar = []
979  nonConstantVar = []
980  decoratorLst = extractDecorator(root)
981  #保留Import语句、函数和类定义
982  for node in root.body:
983  #增加AsyncFunctionDef节点信息保存 -- 2025/5/19
984  if isinstance(node,ast.Import) or isinstance(node,ast.ImportFrom) or isinstance(node,ast.ClassDef) or isinstance(node,ast.FunctionDef) or isinstance(node,ast.AsyncFunctionDef):
985  newBody.append(node)
986 
987  #保留包含常量的全局赋值语句和装饰器调用相关的赋值语句,例如:
988  # Case 1:
989  #1. a = 1
990  #2. b = 1
991  #3. c = a + b
992  #4. c = func(a,b)
993  #仅保留1,2,3行代码
994  # Case 2:
995  #1. app = Flask(__name__)
996  #2. @app.route("/")
997  #保留 app = Flask(__name__)以解决NameError
998  if isinstance(node,ast.Assign):
999  result = saveConstantAssign(node,constantVar,nonConstantVar,decoratorLst)
1000  if result==1:
1001  continue
1002  else:
1003  newBody+=result
1004 
1005  #保留包含常量的局部赋值语句
1006  for node in ast.walk(root):
1007  if isinstance(node,ast.Assign):
1008  result = saveConstantAssign(node,constantVar,nonConstantVar,decoratorLst)
1009  if result==1:
1010  continue
1011  else:
1012  for item in result:
1013  if ast.dump(item) not in [ast.dump(i) for i in newBody]:
1014  newBody.append(item)
1015 
1016  root.body=newBody
1017  newFile=ast.unparse(root)
1018  with open(file,'w',encoding='utf-8') as fw:
1019  fw.write(f"{newFile}\n")
1020 
1021 
1022 
1023 
1029 def ignore_sym_links(directory, files):
1030  return [f for f in files if os.path.islink(os.path.join(directory, f))]
1031 
1032 
1033 
1034 
1040 def getLibImportLst(projPath,libName):
1041  lst=[]
1042  pathObj=Path('DF')
1043  pathObj.getPath(projPath)
1044  filePath=[file for file in pathObj.path if file.endswith('.py')]
1045  pattern=rf"(from {libName}|import {libName})" #确保库的前面不会出现其它字符
1046  for file in filePath:#下面的所有操作都是对项目副本进行的
1047  # with open(file,'r') as fr:
1048  with open(file, 'r', encoding='UTF-8') as fr:
1049  code=fr.read()
1050  try:
1051  root=ast.parse(code,filename='<unknown>',mode='exec')
1052  for node in ast.walk(root):
1053  if isinstance(node,ast.Import) or isinstance(node,ast.ImportFrom):
1054  s=ast.unparse(node)
1055  if bool(re.search(pattern,s)):
1056  lst.append(s)
1057  except Exception as e:
1058  print(f"getLibImportLst: ast parse failed, {file}, {e}")
1059 
1060 
1061  ansLst=list(set(lst))
1062  ansLst.sort(key=lst.index)
1063  return ansLst
1064 
1065 
1066 
1067 
1071 def convertTabsToSpaces(directory):
1072  for root, dirs, files in os.walk(directory):
1073  for file in files:
1074  if file.endswith('.py'):
1075  file_path = os.path.join(root, file)
1076  try:
1077  with open(file_path, 'r', encoding='utf-8') as f:
1078  content = f.read()
1079  # 将制表符转换为4个空格
1080  content = content.expandtabs(4)
1081  with open(file_path, 'w', encoding='utf-8') as f:
1082  f.write(content)
1083  except Exception as e:
1084  print(f"Error converting file {file_path}: {e}")
1085 
1086 
1087 
1088 
1094 def writeRecordValue(filePath,pklRelPath,useCallsiteName):
1095  scriptPath=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),'Script','recordValue.py')
1096  with open(scriptPath,'r',encoding='UTF-8') as fr:
1097  content=fr.read()
1098  content=content.replace("'__PCART_PKL_REL_PATH__'",repr(pklRelPath))
1099  content=content.replace('__PCART_USE_CALLSITE_NAME__',str(bool(useCallsiteName)))
1100  with open(filePath,'w',encoding='UTF-8') as fw:
1101  fw.write(content)
1102 
1103 
1104 
1109 def scriptPath(scriptName):
1110  return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),'Script',scriptName)
1111 
1112 
1113 
1120 def restoreProjectCopy(projName,copyRoot,tempDir):
1121  os.makedirs(tempDir,exist_ok=True)
1122  tempProjPath=os.path.join(tempDir,projName)
1123  if os.path.exists(tempProjPath):
1124  shutil.rmtree(tempProjPath)
1125  shutil.move(os.path.join(copyRoot, projName), tempProjPath)
1126  shutil.move(os.path.join(copyRoot, f'bak_{projName}'), os.path.join(copyRoot, projName))
1127  shutil.move(tempProjPath, os.path.join(copyRoot, f'bak_{projName}'))
1128 
1129 
1130 
1150 def codeProcess(projPath,runCommand,runPath,libName,workspace,pcresolveLookup=None):
1151  runtimePaths=getRuntimePaths(workspace)
1152  copyRoot=runtimePaths['copy_root']
1153  dynamicRoot=runtimePaths['dynamic_root']
1154  dataDir=runtimePaths['data_dir']
1155  #提取运行的文件
1156  runFileLst=[]
1157  runFile=getRunFile(runCommand)
1158  prefix='' #运行文件所在的目录,默认是在项目的一级子目录下
1159  # if '/' in runFile:
1160  if runFile and ('/' in runFile or '\\' in runFile):
1161  # prefix=runFile.rsplit('/',1)[0]
1162  prefix = os.path.dirname(runFile)
1163  # runFile=runFile.rsplit('/',1)[1] #去掉路径前缀,只保留文件名即run.py
1164  runFile = os.path.basename(runFile)
1165  if runFile:
1166  runFileLst.append(runFile)
1167  #这种情况针对于python run.py, run.py在其它目录中比如src,则prefix就是src
1168  #若是python src/run.py, 则prexfix和runPath是一致的
1169  if runPath!='' and prefix!=runPath:
1170  prefix=runPath
1171  # projName=projPath.split('/')[-1]
1172  projName = os.path.basename(projPath)
1173  copyProjPath=os.path.join(copyRoot,projName)
1174  importStatement=''
1175  for it in runFileLst:
1176  # it=it.rstrip('.py') #把文件名中的后缀去掉,但遇到display.py会变成displa
1177  it=it[0:-3]
1178  importStatement+=f"from {it} import *\n"
1179 
1180 
1181  #找出项目中所有和第三方库相关的import语句
1182  libImportLst=getLibImportLst(projPath,libName)
1183  libImportLst.append(importStatement)
1184 
1185  #清除Copy和Dynamic中遗留的项目信息,然后把新项目的信息拷贝进去
1186  if os.path.isdir(copyRoot):
1187  shutil.rmtree(copyRoot)
1188  os.makedirs(os.path.dirname(copyRoot) or '.',exist_ok=True)
1189  shutil.copytree(projPath,os.path.join(copyRoot,projName),ignore=ignore_sym_links)
1190  os.mkdir(os.path.join(copyRoot,'pkl'))
1191  if os.path.isdir(dynamicRoot):
1192  shutil.rmtree(dynamicRoot)
1193  os.makedirs(os.path.dirname(dynamicRoot) or '.',exist_ok=True)
1194  shutil.copytree(projPath,os.path.join(dynamicRoot,projName),ignore=ignore_sym_links)
1195 
1196  #去掉项目代码中的冗余信息,仅保存项目代码的结构信息(import,functionDef, classDef)
1197  saveStructure(os.path.join(dynamicRoot,projName),libName)
1198 
1199  dynamicScriptDir=os.path.join(dynamicRoot,projName,prefix)
1200  shutil.copy2(scriptPath('addValueForAPI.py'),dynamicScriptDir)
1201  shutil.copy2(scriptPath('codeUtils.py'),dynamicScriptDir)
1202  shutil.copy2(scriptPath('dynamicMatch.py'),dynamicScriptDir)
1203  shutil.copy2(scriptPath('verifySingle.py'),dynamicScriptDir)
1204 
1205  #更新脚本中的from ... import ...语句,因为加载pkl的时候需要依赖于项目的结构信息
1206  modifyFromImport(os.path.join(dynamicScriptDir,'addValueForAPI.py'),libImportLst)
1207  modifyFromImport(os.path.join(dynamicScriptDir,'dynamicMatch.py'),libImportLst)
1208  modifyFromImport(os.path.join(dynamicScriptDir,'verifySingle.py'),libImportLst)
1209 
1210  #清除data中的数据
1211  if os.path.isdir(dataDir):
1212  shutil.rmtree(dataDir)
1213  os.makedirs(dataDir)
1214 
1215 
1216  #然后再把Copy中的项目制表符统一转化为空格,目的是为了插入字典的时候计算空格缩进
1217  convertTabsToSpaces(copyRoot)
1218 
1219  #把代码换行写的合成一行,并添加字典
1220  pathObj=Path('DF')
1221  pathObj.getPath(copyProjPath)
1222  filePath=[file for file in pathObj.path if file.endswith('.py')]
1223  for file in filePath:#下面的所有操作都是对项目副本进行的
1224  oneLine(file)
1225 
1226  #处理单行条件返回语句
1227  for file in filePath:
1229 
1230  #处理局部变量
1231  for file in filePath:
1232  convertLocalVar(file,libName)
1233 
1234 
1235  shutil.copytree(os.path.join(copyRoot,projName),os.path.join(copyRoot,f'bak_{projName}'))
1236 
1237  # 计算recordValue.py到Copy/pkl的相对路径
1238  pklDepth=1
1239  if prefix:
1240  pklDepth+=len([s for s in prefix.replace('\\','/').strip('/').split('/') if s])
1241  pklRelPath='/'.join(['..']*pklDepth+['pkl'])
1242  writeRecordValue(os.path.join(copyRoot,f'bak_{projName}',prefix,'recordValue.py'),pklRelPath,0)
1243 
1244 
1245  for file in filePath:
1246  if pcresolveLookup is None:
1247  addDictAll(projPath,projName,file,copyRoot,runFileLst,libName,runPath,runCommand)
1248  else:
1249  addDictAll(projPath,projName,file,copyRoot,runFileLst,libName,runPath,runCommand,pcresolveLookup=pcresolveLookup)
1250 
1251  #再对bak_proj中的运行文件进行插桩
1252  for file in runFileLst:
1253  file=os.path.join(copyRoot,f'bak_{projName}',prefix,file)
1254  handleRunFile(file,runPath,runCommand)
1255  # bak项目会在current pkl生成后换回Copy/{projName},其运行文件也依赖codeUtils
1256  shutil.copy2(scriptPath('codeUtils.py'),os.path.join(copyRoot,f'bak_{projName}',prefix))
1257 
1258  #处理完项目所有文件后,再给项目添加一个新的文件
1259  writeRecordValue(os.path.join(copyRoot,projName,prefix,'recordValue.py'),pklRelPath,1)
1260 
1261  shutil.copy2(scriptPath('codeUtils.py'),os.path.join(copyRoot,projName,runPath))
1262  if prefix!=runPath:
1263  shutil.copy2(scriptPath('codeUtils.py'),os.path.join(copyRoot,projName,prefix))
def getCallFunction(filePath, libName, projPath=None, pcresolveLookup=None)
Extract all API calls from a given .py file 每次传进来一个.py文件,抽取所有的调用API.
Definition: getCall.py:205
def modifyWithName(callName, withitemCallName, lineno=None)
Restore the conventional API call path in the withitem (including AsyncWith) call form 还原withitemAPI调...
Definition: getCall.py:157
def convertLocalVar(filePath, libName)
Convert the ListComp and DictComp statements to variable assignment statements 将列表推导式listComp和字典推导式Di...
Definition: preprocess.py:156
def codeProcess(projPath, runCommand, runPath, libName, workspace, pcresolveLookup=None)
Code processing 代码预处理
Definition: preprocess.py:1150
def convertTabsToSpaces(directory)
Convert tabs to spaces in all Python files within a directory 将目录下所有Python文件中的制表符转换为空格
Definition: preprocess.py:1071
def getDictVar(root, ansLst)
Get the local variable in the dictionary comprehension 获取字典推导式中的局部变量
Definition: preprocess.py:132
def oneLine(filePath)
Convert the multi-line parameter calls in the code into a single line to facilitate insertion into di...
Definition: preprocess.py:66
def expandConditionalReturn(filePath)
Expand the single-line conditional return statement into a multi-line if-else structure 展开单行条件retur...
Definition: preprocess.py:82
def obtainDef(sourcePath)
Extract class and function definitions from a source file 从源文件中提取类和函数定义
Definition: preprocess.py:852
def addDictSingle(callAPI, filePath, callKey)
Code instrumentation for a single API call within a source file 源文件单个API调用代码插桩
Definition: preprocess.py:477
def getImportLine(codeLst)
Determine the instrumentation line for import statements 确定import语句插桩行
Definition: preprocess.py:404
def writeRecordValue(filePath, pklRelPath, useCallsiteName)
Write recordValue.py used by instrumented project files 写入插桩文件共享的recordValue.py.
Definition: preprocess.py:1094
def getLibImportLst(projPath, libName)
Get all lib-related import statements 获取所有第三方库相关的import语句
Definition: preprocess.py:1040
def restoreProjectCopy(projName, copyRoot, tempDir)
Restore the project copy prepared for single-API instrumentation 恢复用于单个API插桩的项目副本
Definition: preprocess.py:1120
def getListVar(root, ansLst)
Get the local variable in the list comprehension 获取列表推导式中的局部变量
Definition: preprocess.py:105
def countBracket(s)
Count the number of different types of brackets ((), [], {}) in a string 计算字符串中各类括号((),...
Definition: preprocess.py:31
def getSelfReceiverExpr(root, lineno, methodName)
Resolve the visible base-class expression for an inherited self receiver 还原继承场景中self调用者可见的基类表达式
Definition: preprocess.py:361
def scriptPath(scriptName)
Get source script path from PCART repository 获取PCART仓库中的辅助脚本路径
Definition: preprocess.py:1109
def countSpace(s)
Count the number of spaces at the beginning of a string 计算字符串的前面有多少个空格
Definition: preprocess.py:385
def saveConstantAssign(astNode, constantVar, nonConstantVar, decoratorLst)
Save assignment statement with constant values 保存常量赋值语句
Definition: preprocess.py:907
def addDictAll(projPath, projName, filePath, copyRoot, runFileLst, libName, runPath, runCommand, pcresolveLookup=None)
Code instrumentation for all API calls within a project source file 项目源文件所有API调用代码插桩
Definition: preprocess.py:620
def modifyFromImport(filePath, importStatement)
Save import statement to source file 将import语句保存到源码中
Definition: preprocess.py:873
def getAssignReceiverExpr(root, source, receiverName, lineno)
Resolve the source expression assigned to a receiver alias 还原调用者别名在当前作用域内的赋值来源表达式
Definition: preprocess.py:276
def ignore_sym_links(directory, files)
Determine the soft link files in a directory 确定文件夹中的软链接文件
Definition: preprocess.py:1029
def handleRunFile(file, runPath, runCommand)
Code instrumentation for project run file 项目运行文件代码插桩
Definition: preprocess.py:836
def findAssignCall(root)
Find assignment call statements 获取赋值调用语句
Definition: preprocess.py:259
def extractDecorator(root)
Extract decorator API calls 抽取项目中第三方库装饰器调用
Definition: preprocess.py:432
def saveStructure(projPath, libName)
Save structure information of the project 保存项目的结构信息
Definition: preprocess.py:962
def getDecoratorInsertIndex(codeLst, index)
Get decorator block start index 获取装饰器块起始行号
Definition: preprocess.py:453
def getAst(filePath, strFlag=0)
Get AST for code 将代码转化为Ast树
Definition: tool.py:172
def getParameter(p_string, separator=',', space=1)
Split parameter string into list of separated parameters 将参数字符串拆分成单个的参数
Definition: tool.py:84
def departAPI2(s, separator='.')
Split API call string based on separator "." 根据"."拆分API调用字符串
Definition: tool.py:339
def departAPI(s)
Split API call string based on parameter passing 根据参数传递拆分API调用字符串
Definition: tool.py:291
def getRunFile(runCommand)
Extract the .py script filename from a run command 从运行命令中提取.py脚本文件名
Definition: tool.py:785
def getLastAPIParameter(apiStr)
Get parameter(s) of the last API from the API call string 获取最后一个API参数
Definition: tool.py:261
def getRuntimePaths(workspace)
Return runtime artifact paths for the current execution 返回当前执行使用的运行产物路径
Definition: workspace.py:197