19 from Map.map
import mapAPI
20 from multiprocessing
import Pool
21 from multiprocessing
import Manager
22 from Extract.getCall
import getCallFunction
23 from Extract.pcresolveBridge
import buildCallsiteLookup
24 from Preprocess.preprocess
import codeProcess,restoreProjectCopy
25 from Repair.repair
import repairTask,validateByRun
26 from Repair.patch
import makePatchEdit,writeRepairArtifacts
27 from Tool.tool
import getAst,save2txt,loadConfig,removeParameter,buildRunCommand,resolveConfigFilePath,resolveConfigValuePath
28 from Tool.workspace
import cleanupRunWorkspace,createRunWorkspace,exportRunReport,getRepoRoot,getRuntimePaths,workspaceCwd
29 from Change.changeAnalyze
import analyzeMatchCompatibility,addValueForAPI,updateSharedDict,querySharedDict,updateErrorLst
43 projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths=args
46 projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths,pcresolveLookup=args
47 copyRoot=runtimePaths[
'copy_root']
48 dataDir=runtimePaths[
'data_dir']
49 reportDir=runtimePaths[
'report_dir']
51 fileName = os.path.basename(file)[:-3]
55 normalized_file = file.replace(
'\\',
'/')
56 tempLst = normalized_file.split(
'/')
57 pos=tempLst.index(projName)
58 realProjPath=
'/'.join(tempLst[0:pos+1])
59 fileRelativePath=
'/'.join(tempLst[pos:])
60 copyFile = os.path.join(copyRoot, *fileRelativePath.split(
'/'))
62 callAPIDict,_=
getCallFunction(file,libName,realProjPath,pcresolveLookup=pcresolveLookup)
63 os.makedirs(dataDir, exist_ok=
True)
64 with open(os.path.join(dataDir, f
'{fileName}_callAPIDict.json'),
'w', encoding=
'utf-8')
as fw:
65 json.dump(callAPIDict, fw, indent=4, ensure_ascii=
False)
70 except Exception
as e:
72 invokedAPINum=len(callAPIDict)
73 errorLog = os.path.join(reportDir, f
'{projName}_fixed_log.txt')
74 for key,record
in callAPIDict.items():
77 callAPI=record[
'call_text']
78 lineNum=record[
'lineno']
79 formatAPI=record[
'format_api']
81 ansDict[key][
'Invoked API']=callAPI
82 ansDict[key][
'Location']=f
"At Line {lineNum} in {fileRelativePath}"
84 if callKey
not in coverSet:
85 ansDict[key][
'Coverage']=
'No'
88 ansDict[key][
'Coverage']=
'Yes'
95 currentMatch=matchDict[
'current']
96 targetMatch=matchDict[
'target']
98 currentMatch=
mapAPI(callAPI,runCommand,runPath,formatAPI,projName,libName,copyFile,currentVersion,currentEnv,lock,errLst,callKey=callKey,runtimePaths=runtimePaths)
99 targetMatch=
mapAPI(callAPI,runCommand,runPath,formatAPI,projName,libName,copyFile,targetVersion,targetEnv,lock,errLst,curr=0,callKey=callKey,runtimePaths=runtimePaths)
104 ansDict[key][f
"Definition @{currentVersion} <{currentMatch['matchMethod']}>"]=str(currentMatch[
'match'])
105 ansDict[key][f
"Definition @{targetVersion} <{targetMatch['matchMethod']}>"]=str(targetMatch[
'match'])
108 repairLst=
analyzeMatchCompatibility(currentMatch,targetMatch,formatAPI=formatAPI,libName=libName,currentVersion=currentVersion,targetVersion=targetVersion)
110 ansDict[key][
'Compatible']=
"Unknown"
112 errorMsg = f
"Error occurred, please check the {projName}_fixed_log.txt"
117 if len(repairLst)==0:
118 ansDict[key][
'Compatible']=
'Yes'
121 ansDict[key][
'Compatible']=
'Unknown'
122 ansDict[key][
'Repair <Unknown>']=
'AST parse failed'
123 errLst.append(f
"{callAPI}, AST parse failed in {fileRelativePath}: {astError}\n")
125 apiWithValue=
addValueForAPI(callAPI,projName,runPath,runCommand,currentEnv,targetEnv,errLst,callKey=callKey,runtimePaths=runtimePaths)
126 fixedAPI,compatibilityLabel,repairStatus=
repairTask(root,callAPI,apiWithValue,projName,runPath,runCommand,repairLst,targetEnv,errLst,callKey=callKey,runtimePaths=runtimePaths)
127 if compatibilityLabel==
'Compatible':
128 ansDict[key][
'Compatible']=
'Yes'
130 if compatibilityLabel==
'Incompatible':
131 ansDict[key][
'Compatible']=
'No'
133 ansDict[key][
'Compatible']=
'Unknown'
135 if repairStatus==
'Successful':
136 ansDict[key][
'Repair <Successful>']=f
"{fixedAPI}"
137 elif repairStatus==
'Failed':
138 ansDict[key][
'Repair <Failed>']=f
"{fixedAPI}"
140 ansDict[key][
'Repair <Unknown>']=f
"{fixedAPI}"
141 if compatibilityLabel==
'Incompatible':
142 patchStatus=repairStatus
if repairStatus
in (
'Successful',
'Failed')
else 'Unknown'
145 patchEdits.append(edit)
149 errorMsg = f
"Error occurred, please check the {projName}_fixed_log.txt"
154 return ansDict,fileRelativePath,invokedAPINum,patchEdits
172 def backward(projPath,libName,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,workspace,pcresolveLookup=None,writePatch=False):
174 copyRoot=runtimePaths[
'copy_root']
175 dataDir=runtimePaths[
'data_dir']
176 tempDir=runtimePaths[
'temp_dir']
177 reportDir=runtimePaths[
'report_dir']
179 pathObj.getPath(projPath)
180 filePath=[it
for it
in pathObj.path
if it.endswith(
'py')]
181 projName=os.path.basename(projPath)
182 errorLog = os.path.join(reportDir, f
'{projName}_fixed_log.txt')
183 if os.path.exists(errorLog):
188 if runPath
and runPath
not in runCommand:
189 cwd = os.path.join(copyRoot, projName, runPath)
191 cwd = os.path.join(copyRoot, projName)
192 print(
'Running the project...')
194 createResult = subprocess.run(cmd, cwd=cwd, capture_output=
True, text=
True, encoding=
'utf-8')
195 if createResult.returncode!=0:
196 print(f
'Failure to generate PKL in current version')
197 print(createResult.stderr)
199 print(
"Running complete")
206 if pcresolveLookup
is None:
212 coverSet_path = os.path.join(copyRoot,
'pkl',
'coverSet')
213 if os.path.exists(coverSet_path):
214 with open(coverSet_path,
'r', encoding=
'utf-8')
as fr:
215 tempLst=fr.readlines()
217 it=it.rstrip(
'\n').replace(
' ',
'')
221 sharedDict=manager.dict()
222 tasks=[(projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths,pcresolveLookup)
for file
in filePath]
223 pool=Pool(processes=1)
224 resultLst=pool.map(backwardTask,tasks)
227 save2txt(resultLst, libName, runCommand, os.path.join(reportDir, f
'{projName}.txt'))
229 patchEdits=[edit
for result
in resultLst
for edit
in result[3]]
248 def run(config,cleanWorkspace=False,writePatch=False):
253 projPath,runCommand,runPath,libName,currentVersion,targetVersion,currentEnv,targetEnv=
loadConfig(configPath)
258 workspace=
createRunWorkspace(repoRoot,projPath,runCommand,runPath,libName,currentVersion,targetVersion,currentEnv,targetEnv,writePatch=writePatch)
259 print(f
"Run workspace: {workspace.workspace_root}")
260 print(
"Code preprocessing...")
266 codeProcess(projPath,runCommand,runPath,libName,workspace=workspace,pcresolveLookup=pcresolveLookup)
267 print(
"Code preprocess complete")
270 succeeded=
backward(projPath,libName,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,workspace=workspace,pcresolveLookup=pcresolveLookup,writePatch=writePatch)
273 print(f
"Report output: {workspace.report_root}")
274 if cleanWorkspace
and succeeded:
276 print(f
"Run workspace removed: {workspace.run_root}")
283 parser=argparse.ArgumentParser(description=
'Python API compatibility analysis and repair tool')
284 parser.add_argument(
'-cfg',dest=
'config',required=
True,help=
'Configuration file path or file name under Configure')
285 parser.add_argument(
'--clean-workspace',action=
'store_true',help=
'Remove the run workspace after successful report export')
286 parser.add_argument(
'--write-patch',action=argparse.BooleanOptionalAction,default=
False,help=
'Generate repair patches and a Successful-only project copy (default: disabled)')
287 args=parser.parse_args()
291 run(args.config,cleanWorkspace=args.clean_workspace,writePatch=args.write_patch)
294 print(f
"Total run time={int(end-start)}s")
297 if __name__==
'__main__':
def analyzeMatchCompatibility(current, target, *formatAPI, libName, currentVersion, targetVersion)
Analyze compatibility after selecting comparable matching results 选择可比较的匹配结果,再分析版本间兼容性
def updateErrorLst(errorLog, errorLst)
Save error messages 保存错误信息
def updateSharedDict(callAPI, currentDict, targetDict, sharedDict)
Update API mapping dictionary: add and revise 更新API映射字典: 添加和修改
def querySharedDict(callAPI, sharedDict)
Query API mapping dictionary 查询API映射字典
def addValueForAPI(callAPI, projName, runPath, runCommand, currentEnv, targetEnv, errLst, callKey, *runtimePaths)
Add values stored by pkl file for API parameters 为API参数添加保存至pkl文件中的值
def getCallFunction(filePath, libName, projPath=None, pcresolveLookup=None)
Extract all API calls from a given .py file 每次传进来一个.py文件,抽取所有的调用API.
def backwardTask(args)
One process handles one file 一个进程处理一个文件
def backward(projPath, libName, currentVersion, currentEnv, targetVersion, targetEnv, runCommand, runPath, workspace, pcresolveLookup=None, writePatch=False)
Generate pkl files and perform detection and repair tasks 生成项目调用API的pkl文件以及执行检测与修复任务
def run(config, cleanWorkspace=False, writePatch=False)
Run PCART with an isolated workspace 在隔离工作区中运行PCART.
def main()
Main function of PCART PCART主函数
def mapAPI(callAPI, runCommand, runPath, formatAPI, projName, libName, copyFile, version, virtualEnv, lock, errLst, curr=1, *callKey, runtimePaths)
Construct the mapping between the invoked API and the lib API to obtain its signature 建立invoked API与 ...
def makePatchEdit(record, fixedAPI, repairStatus)
Collect one unambiguous repair result with its original source span 收集唯一修复表达式及其原始源码范围
def writeRepairArtifacts(projPath, projName, edits, reportDir)
Write independent category patches and a Successful-only project copy 写出三类独立补丁及仅含Successful修复的项目副本
def buildCallsiteLookup(projPath, libName)
Build a CallsiteRecord lookup table from PCResolve analysis 从PCResolve分析结果构建CallsiteRecord查找表
def codeProcess(projPath, runCommand, runPath, libName, workspace, pcresolveLookup=None)
Code processing 代码预处理
def restoreProjectCopy(projName, copyRoot, tempDir)
Restore the project copy prepared for single-API instrumentation 恢复用于单个API插桩的项目副本
def repairTask(root, callAPI, apiWithValue, projName, runPath, runCommand, repairLst, virtualEnv, errLst, callKey, *runtimePaths)
Task of repairing parameter compatibility issues 参数兼容性问题修复任务
def getRepoRoot()
Get PCART repository root path 获取PCART仓库根目录路径
def cleanupRunWorkspace(workspace)
Remove one completed PCART run workspace 删除一次已完成的PCART运行工作区
def exportRunReport(workspace)
Export internal report files to user-visible report directory 将工作区内部报告导出到用户可见报告目录
def createRunWorkspace(repoRoot, projPath, runCommand, runPath, libName, currentVersion, targetVersion, currentEnv, targetEnv, commandId='cmd-001', timestamp=None, writePatch=False)
Create an isolated workspace for one PCART run command 为一次PCART运行命令创建隔离工作区
def getRuntimePaths(workspace)
Return runtime artifact paths for the current execution 返回当前执行使用的运行产物路径