├── Go-IR ├── README.md ├── build │ └── lib.linux-x86_64-2.7 │ │ └── go2ir │ │ ├── __init__.py │ │ ├── common.py │ │ └── main.py ├── dist │ └── Go2IR-0.0.1-py2.7.egg ├── setup.py └── src │ ├── Go2IR.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── entry_points.txt │ └── top_level.txt │ └── go2ir │ ├── __init__.py │ ├── common.py │ └── main.py ├── README.md ├── argparser └── parser.go ├── capture ├── capture.go └── util.go ├── main.go ├── mycontext └── mycontext.go ├── optimization ├── opimization.go └── util.go ├── report └── report.go ├── res ├── case-kubernetes.txt ├── fail.png └── passed.png ├── util ├── methods.go └── model.go └── verification ├── util.go └── verification.go /Go-IR/README.md: -------------------------------------------------------------------------------- 1 | # Go2IR 2 | Go2IR is a tool for converting a go language program project into LLVM IR code based on the "gollvm" tool. 3 | 4 | # Get Started 5 | * install gollvm. 6 | * install python. 7 | * run command "sudo python setup.py install". 8 | * install missing dependencies if any warnings. 9 | * run command "Go2IR -p {project path} -m {main package path}" 10 | -------------------------------------------------------------------------------- /Go-IR/build/lib.linux-x86_64-2.7/go2ir/__init__.py: -------------------------------------------------------------------------------- 1 | from main import main -------------------------------------------------------------------------------- /Go-IR/build/lib.linux-x86_64-2.7/go2ir/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # =============================== # 4 | # Author : Cong Wang # 5 | # Email : bryantwangcong@163.com # 6 | # =============================== # 7 | 8 | import os 9 | import shutil 10 | 11 | # create directory, remove if exist 12 | def setDir(dir_path): 13 | if os.path.exists(dir_path): 14 | shutil.rmtree(dir_path) 15 | os.mkdir(dir_path) 16 | 17 | 18 | # findGoSrc : return True if find go source file in ${file_list} 19 | def findGoSrc(file_list): 20 | for file in file_list: 21 | if os.path.exists(file) and os.path.isfile(file) and file.endswith(".go"): 22 | return True 23 | return False 24 | -------------------------------------------------------------------------------- /Go-IR/build/lib.linux-x86_64-2.7/go2ir/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # =============================== # 4 | # Author : Cong Wang # 5 | # Email : bryantwangcong@163.com # 6 | # =============================== # 7 | 8 | import os 9 | import re 10 | import logging 11 | import argparse 12 | import subprocess 13 | from common import findGoSrc, setDir 14 | 15 | LOGGER_LEVEL = 10 # 10: debug, 20: info 16 | 17 | # Overall Handler for the task 18 | class Go2IrHandler(object): 19 | 20 | 21 | def __init__(self, args): 22 | # basic member variables 23 | self.args = args 24 | self.project = args.project 25 | self.output = args.output if args.output != "" else os.path.join(os.getcwd(), "../go2ir_output") 26 | self.logger = logging.getLogger("default") 27 | self.error_status = False 28 | self.unique_ll_id = 1 29 | self.commit_ids = [] 30 | self.commit_update_files = [] 31 | self.curr_commit_idx = 0 32 | self.modified_files = [] 33 | 34 | # basic initialization 35 | self.logger_init() 36 | self.output_init() 37 | self.check_args() 38 | 39 | 40 | # logger_init : logger initialization 41 | def logger_init(self): 42 | # logger level and format 43 | self.logger.setLevel(LOGGER_LEVEL) 44 | log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 45 | 46 | # declare the console logger 47 | console_handler = logging.StreamHandler() 48 | console_handler.setLevel(LOGGER_LEVEL) 49 | console_handler.setFormatter(log_format) 50 | self.logger.addHandler(console_handler) 51 | 52 | 53 | # output_init : output initialization 54 | def output_init(self): 55 | setDir(self.output) 56 | 57 | 58 | # check_args : check the path of ${project} 59 | def check_args(self): 60 | if not(os.path.exists(self.project) and os.path.isdir(self.project)): 61 | self.logger.error("Path of ${project} is invalid: %s" % (self.project)) 62 | self.error_status = True 63 | else: 64 | self.logger.debug("Path of ${project} is valid: %s" % (self.project)) 65 | 66 | 67 | # run : execution 68 | def run(self): 69 | self.logger.debug(self.args) 70 | setDir(self.output) 71 | self.handle_dir(os.path.abspath(self.project)) 72 | 73 | 74 | # handle_dir : handler directory recursively 75 | def handle_dir(self, dir): 76 | files = os.listdir(dir) 77 | file_paths = [os.path.join(dir, file) for file in files] 78 | 79 | if findGoSrc(file_paths): 80 | # if we find go source files, this part is significant! 81 | build_script = "go build -work -x *.go 1> transcript.txt 2>&1" 82 | self.logger.debug("%s ===> %s" % (build_script, dir)) 83 | subprocess.Popen(build_script, cwd=dir, shell=True).wait() 84 | 85 | transcript_path = os.path.join(dir, "transcript.txt") 86 | new_script = [] 87 | with open(transcript_path) as f: 88 | trans_text = f.read().split("\n") 89 | if trans_text[0].startswith('WORK='): 90 | new_script.append(trans_text[0]) 91 | pattern_cd = "cd %s" % dir 92 | for idx, text in enumerate(trans_text): 93 | if text == pattern_cd and trans_text[idx+1].find("llvm-goc") >= 0: 94 | modified_script = trans_text[idx+1] + " -S -emit-llvm" 95 | modified_script = modified_script.replace("-o $WORK/b001/_go_.o", "-o %s/%s.ll" % (self.output, self.unique_ll_id)) 96 | new_script.append(modified_script) 97 | break 98 | 99 | if len(new_script) == 2: 100 | self.unique_ll_id += 1 101 | generate_script = " && ".join(new_script) 102 | self.logger.debug(generate_script) 103 | subprocess.Popen(generate_script, cwd=dir, shell=True).wait() 104 | else: 105 | self.logger.error(new_script) 106 | 107 | # handle sub-dirs recursively 108 | for file in file_paths: 109 | if os.path.exists(file) and os.path.isdir(file): 110 | dir_name = file[file.rfind("/")+1:] 111 | if dir_name != "vendor": 112 | self.handle_dir(file) 113 | 114 | 115 | def main(): 116 | # Define command line arguments 117 | arg_parser = argparse.ArgumentParser() 118 | arg_parser.add_argument('-p', '--project', help='choose golang project', default='') 119 | arg_parser.add_argument('-o', '--output', help='choose output dir', default='') 120 | args = arg_parser.parse_args() 121 | 122 | handler = Go2IrHandler(args) 123 | handler.run() 124 | 125 | if __name__ == "__main__": 126 | main() -------------------------------------------------------------------------------- /Go-IR/dist/Go2IR-0.0.1-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangcong15/escape-from-escape-analysis-of-golang/4d394d884f88b82bfea6e7380f20e12b9ee10396/Go-IR/dist/Go2IR-0.0.1-py2.7.egg -------------------------------------------------------------------------------- /Go-IR/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name = "Go2IR", 5 | version = "0.0.1", 6 | keywords = ("golang", "llvm ir"), 7 | description = "Go2ir is a tool for converting a go language program project into LLVM IR code based on the \"gollvm\" tool", 8 | long_description = "", 9 | license = "Tsinghua Univ. Licence", 10 | 11 | url = "", 12 | author = "Cong Wang, Yu Jiang, Jian Gao", 13 | author_email = "wangcong15@mails.tsinghua.edu.cn", 14 | 15 | packages = ['go2ir'], 16 | package_dir = {'': 'src'}, 17 | package_data = {}, 18 | include_package_data = False, 19 | platforms = "any", 20 | install_requires = [], 21 | 22 | scripts = [], 23 | entry_points = { 24 | 'console_scripts': [ 25 | 'Go2IR=go2ir.main:main' 26 | ] 27 | } 28 | ) -------------------------------------------------------------------------------- /Go-IR/src/Go2IR.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: Go2IR 3 | Version: 0.0.1 4 | Summary: Go2ir is a tool for converting a go language program project into LLVM IR code based on the "gollvm" tool 5 | Home-page: UNKNOWN 6 | Author: Cong Wang, Yu Jiang, Jian Gao 7 | Author-email: wangcong15@mails.tsinghua.edu.cn 8 | License: Tsinghua Univ. Licence 9 | Description: UNKNOWN 10 | Keywords: golang,llvm ir 11 | Platform: any 12 | -------------------------------------------------------------------------------- /Go-IR/src/Go2IR.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | src/Go2IR.egg-info/PKG-INFO 4 | src/Go2IR.egg-info/SOURCES.txt 5 | src/Go2IR.egg-info/dependency_links.txt 6 | src/Go2IR.egg-info/entry_points.txt 7 | src/Go2IR.egg-info/top_level.txt 8 | src/go2ir/__init__.py 9 | src/go2ir/common.py 10 | src/go2ir/main.py -------------------------------------------------------------------------------- /Go-IR/src/Go2IR.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Go-IR/src/Go2IR.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | Go2IR = go2ir.main:main 3 | 4 | -------------------------------------------------------------------------------- /Go-IR/src/Go2IR.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | go2ir 2 | -------------------------------------------------------------------------------- /Go-IR/src/go2ir/__init__.py: -------------------------------------------------------------------------------- 1 | from main import main -------------------------------------------------------------------------------- /Go-IR/src/go2ir/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # =============================== # 4 | # Author : Cong Wang # 5 | # Email : bryantwangcong@163.com # 6 | # =============================== # 7 | 8 | import os 9 | import shutil 10 | 11 | # create directory, remove if exist 12 | def setDir(dir_path): 13 | if os.path.exists(dir_path): 14 | shutil.rmtree(dir_path) 15 | os.mkdir(dir_path) 16 | 17 | 18 | # findGoSrc : return True if find go source file in ${file_list} 19 | def findGoSrc(file_list): 20 | for file in file_list: 21 | if os.path.exists(file) and os.path.isfile(file) and file.endswith(".go"): 22 | return True 23 | return False 24 | -------------------------------------------------------------------------------- /Go-IR/src/go2ir/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # =============================== # 4 | # Author : Cong Wang # 5 | # Email : bryantwangcong@163.com # 6 | # =============================== # 7 | 8 | import os 9 | import re 10 | import logging 11 | import argparse 12 | import subprocess 13 | from common import findGoSrc, setDir 14 | 15 | LOGGER_LEVEL = 10 # 10: debug, 20: info 16 | 17 | # Overall Handler for the task 18 | class Go2IrHandler(object): 19 | 20 | 21 | def __init__(self, args): 22 | # basic member variables 23 | self.args = args 24 | self.project = args.project 25 | self.output = args.output if args.output != "" else os.path.join(os.getcwd(), "../go2ir_output") 26 | self.logger = logging.getLogger("default") 27 | self.error_status = False 28 | self.unique_ll_id = 1 29 | self.commit_ids = [] 30 | self.commit_update_files = [] 31 | self.curr_commit_idx = 0 32 | self.modified_files = [] 33 | 34 | # basic initialization 35 | self.logger_init() 36 | self.output_init() 37 | self.check_args() 38 | 39 | 40 | # logger_init : logger initialization 41 | def logger_init(self): 42 | # logger level and format 43 | self.logger.setLevel(LOGGER_LEVEL) 44 | log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 45 | 46 | # declare the console logger 47 | console_handler = logging.StreamHandler() 48 | console_handler.setLevel(LOGGER_LEVEL) 49 | console_handler.setFormatter(log_format) 50 | self.logger.addHandler(console_handler) 51 | 52 | 53 | # output_init : output initialization 54 | def output_init(self): 55 | setDir(self.output) 56 | 57 | 58 | # check_args : check the path of ${project} 59 | def check_args(self): 60 | if not(os.path.exists(self.project) and os.path.isdir(self.project)): 61 | self.logger.error("Path of ${project} is invalid: %s" % (self.project)) 62 | self.error_status = True 63 | else: 64 | self.logger.debug("Path of ${project} is valid: %s" % (self.project)) 65 | 66 | 67 | # run : execution 68 | def run(self): 69 | self.logger.debug(self.args) 70 | setDir(self.output) 71 | self.handle_dir(os.path.abspath(self.project)) 72 | 73 | 74 | # handle_dir : handler directory recursively 75 | def handle_dir(self, dir): 76 | files = os.listdir(dir) 77 | file_paths = [os.path.join(dir, file) for file in files] 78 | 79 | if findGoSrc(file_paths): 80 | # if we find go source files, this part is significant! 81 | build_script = "go build -work -x *.go 1> transcript.txt 2>&1" 82 | self.logger.debug("%s ===> %s" % (build_script, dir)) 83 | subprocess.Popen(build_script, cwd=dir, shell=True).wait() 84 | 85 | transcript_path = os.path.join(dir, "transcript.txt") 86 | new_script = [] 87 | with open(transcript_path) as f: 88 | trans_text = f.read().split("\n") 89 | if trans_text[0].startswith('WORK='): 90 | new_script.append(trans_text[0]) 91 | pattern_cd = "cd %s" % dir 92 | for idx, text in enumerate(trans_text): 93 | if text == pattern_cd and trans_text[idx+1].find("llvm-goc") >= 0: 94 | modified_script = trans_text[idx+1] + " -S -emit-llvm" 95 | modified_script = modified_script.replace("-o $WORK/b001/_go_.o", "-o %s/%s.ll" % (self.output, self.unique_ll_id)) 96 | new_script.append(modified_script) 97 | break 98 | 99 | if len(new_script) == 2: 100 | self.unique_ll_id += 1 101 | generate_script = " && ".join(new_script) 102 | self.logger.debug(generate_script) 103 | subprocess.Popen(generate_script, cwd=dir, shell=True).wait() 104 | else: 105 | self.logger.error(new_script) 106 | 107 | # handle sub-dirs recursively 108 | # for file in file_paths: 109 | # if os.path.exists(file) and os.path.isdir(file): 110 | # dir_name = file[file.rfind("/")+1:] 111 | # if dir_name != "vendor": 112 | # self.handle_dir(file) 113 | 114 | 115 | def main(): 116 | # Define command line arguments 117 | arg_parser = argparse.ArgumentParser() 118 | arg_parser.add_argument('-p', '--project', help='choose golang project', default='') 119 | arg_parser.add_argument('-o', '--output', help='choose output dir', default='') 120 | args = arg_parser.parse_args() 121 | 122 | handler = Go2IrHandler(args) 123 | handler.run() 124 | 125 | if __name__ == "__main__": 126 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Escape from Escape Analysis of Golang 2 | This is the source code of our paper submitted to SEIP-ICSE 2020. In this paper, we propose an escape analysis optimization approach for Go programming language (Golang), aiming to save heap memory usage of programs. 3 | 4 | # Get Started 5 | ### install Go-IR 6 | 1. install gollvm. 7 | 2. install python. 8 | 3. run command "cd Go-IR && sudo python setup.py install". 9 | 4. install missing dependencies if any warnings. 10 | 11 | ### install 12 | 1. install golang. 13 | 2. run command "go get github.com/wangcong15/escape-from-escape-analysis-of-golang". 14 | 15 | ### usage 16 | 1. run command "escape-from-escape-analysis-of-golang -p {project} -m {main}". notice that `{project}` is the path of Golang project, and `{main}` is the path of main package. 17 | 2. just watch it running. it prints the optimization cases and validation results. 18 | 3. the execution is totally automatic. 19 | 20 | ### screenshot 21 | The following figure shows the screenshot of a good optimization. Changing the code does no harm to memory safety in this code snippet. Variable `addrObj` is used as a function parameter of function `causeEscape`, which is a synchronous call. Due to the synchronous nature of its function calls, AST-based verification method decide that `obj` (the object which `addrObj` points to) should not be in heap. Thus we can change the statements to optimize the memory usage. 22 | ![Screenshot of a good optimization](res/passed.png) 23 | 24 | On the other hand, when we add a word `go` ahead of this code statement, things will change. Function call `causeEscape` turns to asynchronous. AST-based method cannot make decisions on syntax level. We use the LLVM IR (Intermediate Representation) to analyze the code. We locate the specific file, specific pointer and specific line of code. The goroutine function `causeEscape` has reading operations on the pointer `addrObj`, which makes it a problem that it memory address might be cleaned or covered before these operations end. Thus we decide the optimization in this case is not correct. 25 | ![Screenshot of a bad optimization](res/fail.png) 26 | 27 | With any problems, pleases write issues or contact us (wangcong15@mails.tsinghua.edu.cn). 28 | Thank you very much. -------------------------------------------------------------------------------- /argparser/parser.go: -------------------------------------------------------------------------------- 1 | package argparser 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | 8 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/mycontext" 9 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 10 | ) 11 | 12 | // Parse : the terminal parameters 13 | func Parse(ctx *mycontext.Context) { 14 | // parse user input parameters 15 | flag.StringVar(&ctx.ArgMain, "m", "", "Specify the Main Package") 16 | flag.StringVar(&ctx.ArgPkg, "p", "", "Specify the Checking Package") 17 | flag.Parse() 18 | ctx.PathToLog = make(map[string]string) 19 | ctx.EscapeCases = make(map[string][]util.EscapeCase) 20 | ctx.Counter = 0 21 | ctx.Number = 0 22 | } 23 | 24 | // Checks : validate the parameters 25 | func Checks(ctx *mycontext.Context) { 26 | if ctx.ArgMain == "" || ctx.ArgPkg == "" { 27 | log.Println("usage: go-escape -m main-package -p project-in-gopath") 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /capture/capture.go: -------------------------------------------------------------------------------- 1 | package capture 2 | 3 | import ( 4 | "log" 5 | "path" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/mycontext" 11 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 12 | ) 13 | 14 | // Compile : 15 | func Compile(ctx *mycontext.Context) { 16 | log.Printf("Handling %s", ctx.ArgMain) 17 | // compile the main package 18 | ctx.MainAbsPath = toAbsPath(ctx.ArgMain) 19 | ctx.PathToLog[ctx.MainAbsPath] = getGcLog(ctx.MainAbsPath) 20 | // compile other related package in same project 21 | depsArr := getDeps(ctx.ArgMain, ctx.ArgPkg) 22 | var tempAbsPath string 23 | for _, v := range depsArr { 24 | if !(strings.Contains(v, "vendor") || strings.Contains(v, "thrift_gen")) { 25 | tempAbsPath = toAbsPath(v) 26 | ctx.PathToLog[tempAbsPath] = getGcLog(tempAbsPath) 27 | } 28 | } 29 | } 30 | 31 | // LogAnalysis : 32 | func LogAnalysis(ctx *mycontext.Context) { 33 | for k := range ctx.PathToLog { 34 | parse(ctx.PathToLog[k], k, ctx) 35 | } 36 | } 37 | 38 | func parse(logs, pkgAbsPath string, ctx *mycontext.Context) { 39 | status := 0 40 | var ptrName string 41 | var lineNo int 42 | var filePath string 43 | reg1, _ := regexp.Compile("^(\\.\\/[a-zA-Z0-9].*?):([0-9]+):.*? moved to heap: (.*?)$") 44 | reg2, _ := regexp.Compile("^(\\.\\/[a-zA-Z0-9].*?):([0-9]+):.*?\tfrom (.*?) \\(interface-converted\\) at .*$") 45 | reg3, _ := regexp.Compile("^(\\.\\/[a-zA-Z0-9].*?):([0-9]+):.*?\tfrom (.*?) \\(passed to call\\[argument escapes\\]\\) at .*?:([0-9]+):.*?$") 46 | 47 | for _, v := range strings.Split(logs, "\n") { 48 | if !strings.Contains(v, "\t") && !strings.Contains(v, "escapes to heap") { 49 | status = 0 50 | } 51 | if ans := reg1.FindAllStringSubmatch(v, -1); len(ans) > 0 { 52 | status = 1 53 | } 54 | if ans := reg2.FindAllStringSubmatch(v, -1); len(ans) > 0 && status == 1 { 55 | status = 2 56 | ptrName = ans[0][3] 57 | } 58 | if ans := reg3.FindAllStringSubmatch(v, -1); len(ans) > 0 && status == 2 && ans[0][3] == ptrName { 59 | status = 3 60 | lineNo, _ = strconv.Atoi(ans[0][4]) 61 | filePath = path.Join(pkgAbsPath, ans[0][1]) 62 | newData := util.EscapeCase{ 63 | LineNo: lineNo, 64 | PtrName: ptrName, 65 | } 66 | if _, ok := ctx.EscapeCases[filePath]; ok { 67 | ctx.EscapeCases[filePath] = append(ctx.EscapeCases[filePath], newData) 68 | } else { 69 | ctx.EscapeCases[filePath] = []util.EscapeCase{newData} 70 | } 71 | ctx.Number++ 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /capture/util.go: -------------------------------------------------------------------------------- 1 | package capture 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 8 | ) 9 | 10 | func toAbsPath(path string) (absPath string) { 11 | var goListScript string 12 | goListScript = fmt.Sprintf("go list -e -f {{.Dir}} %s", path) 13 | absPath, _ = util.ExecShell(goListScript) 14 | return 15 | } 16 | 17 | func getGcLog(flagMainPath string) (errStr string) { 18 | var goBuildScript string // 执行编译并获取GC日志的脚本 19 | 20 | goBuildScript = fmt.Sprintf("cd %s && go1.9.3 build -gcflags=\"-m -m\" *.go", flagMainPath) 21 | _, errStr = util.ExecShell(goBuildScript) 22 | return errStr 23 | } 24 | 25 | func getDeps(path, proj string) (depsInProj []string) { 26 | var goListScript, projPrefix, deps string 27 | var depArr []string 28 | 29 | projPrefix = fmt.Sprintf("%s", proj) 30 | goListScript = fmt.Sprintf("go list -e -f {{.Deps}} %s", path) 31 | 32 | deps, _ = util.ExecShell(goListScript) 33 | deps = strings.TrimRight(deps, "]\n") 34 | deps = strings.TrimLeft(deps, "[") 35 | depArr = strings.Split(deps, " ") 36 | for _, v := range depArr { 37 | if strings.HasPrefix(v, projPrefix) { 38 | depsInProj = append(depsInProj, v) 39 | } 40 | } 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/argparser" 5 | ec "github.com/wangcong15/escape-from-escape-analysis-of-golang/capture" 6 | mycontext "github.com/wangcong15/escape-from-escape-analysis-of-golang/mycontext" 7 | co "github.com/wangcong15/escape-from-escape-analysis-of-golang/optimization" 8 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/report" 9 | ) 10 | 11 | var ctx mycontext.Context 12 | 13 | func main() { 14 | // STEP.0 parsing execution params 15 | argparser.Parse(&ctx) 16 | argparser.Checks(&ctx) 17 | 18 | // STEP.1 Escape Capture 19 | ec.Compile(&ctx) 20 | ec.LogAnalysis(&ctx) 21 | 22 | for k := range ctx.EscapeCases { 23 | // STEP.2 Code Optimization & STEP.3 Correctness Verification 24 | co.Optimize(&ctx, k) 25 | } 26 | 27 | // STEP.4 Report 28 | report.Dump(&ctx) 29 | } 30 | -------------------------------------------------------------------------------- /mycontext/mycontext.go: -------------------------------------------------------------------------------- 1 | package mycontext 2 | 3 | import "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 4 | 5 | // Context : global information 6 | type Context struct { 7 | // argument variables 8 | ArgMain string 9 | ArgPkg string 10 | 11 | // inner data 12 | Counter int 13 | Number int 14 | MainAbsPath string 15 | PathToLog map[string]string 16 | EscapeCases map[string][]util.EscapeCase // key: file path 17 | } 18 | -------------------------------------------------------------------------------- /optimization/opimization.go: -------------------------------------------------------------------------------- 1 | package optimization 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/mycontext" 7 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/verification" 8 | ) 9 | 10 | func Optimize(ctx *mycontext.Context, filePath string) { 11 | for i, v := range ctx.EscapeCases[filePath] { 12 | ctx.Counter++ 13 | log.Printf(greenArrow+" Case %v/%v", ctx.Counter, ctx.Number) 14 | log.Printf(greenArrow+" \033[35;4m%s\033[0m", filePath) 15 | rewrite(filePath, v.LineNo, fetchCode(filePath, v)) 16 | ctx.EscapeCases[filePath][i].IsCorrect = verification.Verification(filePath, v) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /optimization/util.go: -------------------------------------------------------------------------------- 1 | package optimization 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 11 | ) 12 | 13 | var redBar string = "\033[31;1m---\033[0m" 14 | var greenBar string = "\033[32;1m+++\033[0m" 15 | var greenArrow string = "\033[32;1m==>\033[0m" 16 | 17 | func fetchCode(filePath string, v util.EscapeCase) string { 18 | b, _ := ioutil.ReadFile(filePath) 19 | rawCode := string(b) 20 | codeSlice := strings.Split(rawCode, "\n") 21 | var finalCodeSlice1 string 22 | vLineNo := v.LineNo - 1 23 | 24 | // concat strings 25 | if vLineNo-3 < 0 { 26 | finalCodeSlice1 = strings.Join(codeSlice[:vLineNo], "\n") + "\n" 27 | } else { 28 | finalCodeSlice1 = strings.Join(codeSlice[vLineNo-3:vLineNo], "\n") + "\n" 29 | } 30 | finalCodeSlice2 := redBar + strconv.Itoa(vLineNo) + codeSlice[vLineNo] + "\n" 31 | cut1 := fmt.Sprintf("\t%sAddr := uintptr(*unsafe.Pointer(%s))", v.PtrName, v.PtrName) 32 | cut2 := fmt.Sprintf("(*OBJECTTYPE)(*unsafe.Pointer(%sAddr))", v.PtrName) 33 | cut3 := strings.ReplaceAll(codeSlice[vLineNo], v.PtrName, cut2) 34 | finalCodeSlice3 := greenBar + strconv.Itoa(vLineNo) + cut1 + "\n" + greenBar + strconv.Itoa(vLineNo+1) + cut3 + "\n" 35 | var finalCodeSlice4 string 36 | if vLineNo+4 > len(codeSlice) { 37 | finalCodeSlice4 = strings.Join(codeSlice[vLineNo+1:], "\n") 38 | } else { 39 | finalCodeSlice4 = strings.Join(codeSlice[vLineNo+1:vLineNo+4], "\n") 40 | } 41 | finalCodeSlice := finalCodeSlice1 + finalCodeSlice2 + finalCodeSlice3 + finalCodeSlice4 42 | fmt.Println("--------------------") 43 | fmt.Println(finalCodeSlice) 44 | fmt.Println("--------------------") 45 | return cut1 + "\n" + cut3 46 | } 47 | 48 | func rewrite(filePath string, lineNo int, newCode string) { 49 | lineNo-- 50 | b, _ := ioutil.ReadFile(filePath) 51 | dumpFile := filePath + ".escape" 52 | rawCode := string(b) 53 | codeSlice := strings.Split(rawCode, "\n") 54 | codeSlice[lineNo] = newCode 55 | stringToWrite := strings.Join(codeSlice, "\n") 56 | ioutil.WriteFile(dumpFile, []byte(stringToWrite), 0644) 57 | log.Printf(greenArrow+" Optimized code is written to \033[35;4m%v\033[0m", dumpFile) 58 | } 59 | -------------------------------------------------------------------------------- /report/report.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/mycontext" 7 | ) 8 | 9 | func Dump(ctx *mycontext.Context) { 10 | if len(ctx.EscapeCases) == 0 { 11 | return 12 | } 13 | log.Println("-------------------------") 14 | var i int = 1 15 | for p := range ctx.EscapeCases { 16 | for _, ec := range ctx.EscapeCases[p] { 17 | log.Printf("Case.%d: %s, Line.%d, %s, Correctness(%v)", i, p, ec.LineNo, ec.PtrName, ec.IsCorrect) 18 | i++ 19 | } 20 | } 21 | log.Println("-------------------------") 22 | } 23 | -------------------------------------------------------------------------------- /res/case-kubernetes.txt: -------------------------------------------------------------------------------- 1 | Case.1: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go, Line.425, &m, Correctness(true) 2 | Case.2: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go, Line.444, &list.Object, Correctness(true) 3 | Case.3: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go, Line.542, &result, Correctness(true) 4 | Case.4: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go, Line.542, &result, Correctness(true) 5 | Case.5: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go, Line.550, &result, Correctness(true) 6 | Case.6: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go, Line.564, &resultFloat, Correctness(true) 7 | Case.7: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go, Line.421, &u, Correctness(true) 8 | Case.8: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.479, &claimToSource, Correctness(true) 9 | Case.9: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.575, &username, Correctness(true) 10 | Case.10: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.575, &username, Correctness(true) 11 | Case.11: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.607, &groups, Correctness(true) 12 | Case.12: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.628, &claimValue, Correctness(true) 13 | Case.13: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go, Line.681, &str, Correctness(true) 14 | Case.14: github.com/kubernetes/kubernetes/staging/src/k8s.io/legacy-cloud-providers/vsphere/vclib/virtualmachine.go, Line.237, &dsMoList, Correctness(true) 15 | Case.15: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go, Line.85, &nw.Property, Correctness(true) 16 | Case.16: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go, Line.85, &nw.Property, Correctness(true) 17 | Case.17: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go, Line.114, &nw.JSONSchemas, Correctness(true) 18 | Case.18: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go, Line.114, &nw.JSONSchemas, Correctness(true) 19 | Case.19: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go, Line.405, &blockOwnerDeletionPtr, Correctness(true) 20 | Case.20: github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go, Line.437, &block, Correctness(true) 21 | Case.21: github.com/kubernetes/kubernetes/test/e2e/framework/config/config.go, Line.192, &defValue, Correctness(true) 22 | Case.22: github.com/kubernetes/kubernetes/test/e2e/framework/config/config.go, Line.198, &defValue, Correctness(true) 23 | Case.23: github.com/kubernetes/kubernetes/test/e2e/framework/config/config.go, Line.202, &defValue, Correctness(true) 24 | Case.24: github.com/kubernetes/kubernetes/test/e2e/framework/config/config.go, Line.206, &defValue, Correctness(true) 25 | Case.25: github.com/kubernetes/kubernetes/test/e2e/framework/config/config.go, Line.210, &defValue, Correctness(true) 26 | Case.26: github.com/kubernetes/kubernetes/cmd/preferredimports/preferredimports.go, Line.242, &aliases, Correctness(true) 27 | Case.27: github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go/util/cert/cert.go, Line.178, &certBuffer, Correctness(true) 28 | Case.28: github.com/kubernetes/kubernetes/test/images/agnhost/guestbook/guestbook.go, Line.96, &responseJSON, Correctness(true) 29 | Case.29: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal.go, Line.85, &nw.Property, Correctness(true) 30 | Case.30: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal.go, Line.85, &nw.Property, Correctness(true) 31 | Case.31: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal.go, Line.114, &nw.JSONSchemas, Correctness(true) 32 | Case.32: github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal.go, Line.114, &nw.JSONSchemas, Correctness(true) 33 | -------------------------------------------------------------------------------- /res/fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangcong15/escape-from-escape-analysis-of-golang/4d394d884f88b82bfea6e7380f20e12b9ee10396/res/fail.png -------------------------------------------------------------------------------- /res/passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangcong15/escape-from-escape-analysis-of-golang/4d394d884f88b82bfea6e7380f20e12b9ee10396/res/passed.png -------------------------------------------------------------------------------- /util/methods.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | func ExecShell(s string) (string, string) { 10 | cmd := exec.Command("/bin/bash", "-c", s) 11 | var stdout, stderr bytes.Buffer 12 | var outStr, errStr string 13 | 14 | cmd.Stdout = &stdout 15 | cmd.Stderr = &stderr 16 | cmd.Run() 17 | outStr, errStr = string(stdout.Bytes()), string(stderr.Bytes()) 18 | outStr = strings.TrimRight(outStr, "]\n") 19 | errStr = strings.TrimRight(errStr, "]\n") 20 | return outStr, errStr 21 | } 22 | -------------------------------------------------------------------------------- /util/model.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type EscapeCase struct { 4 | LineNo int 5 | PtrName string 6 | IsCorrect bool 7 | } 8 | -------------------------------------------------------------------------------- /verification/util.go: -------------------------------------------------------------------------------- 1 | package verification 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "path" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 11 | ) 12 | 13 | var greenArrow string = "\033[32;1m==>\033[0m" 14 | 15 | // irHandler : scan llvm ir, and achieve the verification 16 | func irHandler(irPath string, filePath string, ec util.EscapeCase) bool { 17 | var result bool = false 18 | // read file contents and split into lines 19 | b, err := ioutil.ReadFile(irPath) 20 | if err != nil { 21 | return false 22 | } 23 | funcBodylineStr := string(b) 24 | 25 | // fetch file id 26 | fileName := path.Base(filePath) 27 | var fileID string 28 | dIFileRegexp := regexp.MustCompile("(!.*?) = !DIFile\\(filename: \\\"" + fileName + "\\\", directory: \\\"\\.\\\"\\)") 29 | dIFileParams := dIFileRegexp.FindAllStringSubmatch(funcBodylineStr, -1) 30 | if len(dIFileParams) > 0 { 31 | fileID = dIFileParams[0][1] 32 | } 33 | 34 | var varID string 35 | dILocalVariableRegexp := regexp.MustCompile("(!.*?) = !DILocalVariable\\(name: \\\"" + ec.PtrName + "\\\", .*?, file: " + fileID + ",") 36 | dILocalVariableParams := dILocalVariableRegexp.FindAllStringSubmatch(funcBodylineStr, -1) 37 | if len(dILocalVariableParams) > 0 { 38 | varID = dILocalVariableParams[0][1] 39 | } 40 | 41 | // divide ir into function bodies 42 | funcID2Body := make(map[string]string) 43 | defineFuncRegexp := regexp.MustCompile("define .*? (@[^ ]+)\\(.* (\\{[\\s\\S]*?\n\\})") 44 | defineParams := defineFuncRegexp.FindAllStringSubmatch(funcBodylineStr, -1) 45 | callValueRegexp := regexp.MustCompile("call void @llvm.dbg.value\\(.*?([^ ]+?), .*?" + varID + ",") 46 | var newName string 47 | var storeName string 48 | var srcFuncName string 49 | var goFuncName string 50 | for _, p := range defineParams { 51 | funcID := p[1] 52 | funcBody := p[2] 53 | if goFuncName != "" { 54 | if goFuncName == funcID { 55 | if strings.Contains(funcBody, storeName) { 56 | log.Printf("From Block.%s may ends before Block.%s, thus %s should be saved in heap memory.", srcFuncName, goFuncName, ec.PtrName) 57 | return false 58 | } else { 59 | return true 60 | } 61 | } else { 62 | continue 63 | } 64 | } 65 | funcID2Body[funcID] = funcBody 66 | if !strings.Contains(funcBody, varID+",") { 67 | continue 68 | } 69 | srcFuncName = p[1] 70 | lineArr := strings.Split(funcBody, "\n") 71 | for _, line := range lineArr { 72 | line = strings.TrimSpace(line) 73 | callValueParams := callValueRegexp.FindAllStringSubmatch(line, -1) 74 | if len(callValueParams) > 0 { 75 | newName = callValueParams[0][1] 76 | break 77 | } 78 | } 79 | if newName == "" { 80 | continue 81 | } 82 | storeRegexp := regexp.MustCompile("store .*? " + newName + ", .*? ([^ ]+?),") 83 | goFuncRegexp := regexp.MustCompile("call void @__go_go\\(.*?(@[^ ]+) ") 84 | for _, line := range lineArr { 85 | line = strings.TrimSpace(line) 86 | if storeName == "" { 87 | storeParams := storeRegexp.FindAllStringSubmatch(line, -1) 88 | if len(storeParams) > 0 { 89 | storeName = storeParams[0][1] 90 | continue 91 | } 92 | } else { 93 | goFuncParams := goFuncRegexp.FindAllStringSubmatch(line, -1) 94 | if len(goFuncParams) > 0 { 95 | goFuncName = goFuncParams[0][1] 96 | if v, ok := funcID2Body[goFuncName]; ok { 97 | if strings.Contains(v, storeName) { 98 | log.Printf("From Block.%s may ends before Block.%s, thus %s should be saved in heap memory.", srcFuncName, goFuncName, ec.PtrName) 99 | return false 100 | } else { 101 | return true 102 | } 103 | } 104 | break 105 | } 106 | } 107 | } 108 | 109 | } 110 | return result 111 | } 112 | -------------------------------------------------------------------------------- /verification/verification.go: -------------------------------------------------------------------------------- 1 | package verification 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "io/ioutil" 9 | "log" 10 | "path/filepath" 11 | 12 | "github.com/wangcong15/escape-from-escape-analysis-of-golang/util" 13 | ) 14 | 15 | // Verification : validate the code optimization, whether make damage to memory safety 16 | func Verification(filePath string, ec util.EscapeCase) bool { 17 | if checkAST(filePath, ec) { 18 | return true 19 | } else if checkLLVMIR(filePath, ec) { 20 | return true 21 | } 22 | return false 23 | } 24 | 25 | // Check sync or asyn call of the function, if sync, reply YES!! 26 | func checkAST(filePath string, ec util.EscapeCase) bool { 27 | // generate abstract syntax tree 28 | b, _ := ioutil.ReadFile(filePath) 29 | rawCode := string(b) 30 | fset := token.NewFileSet() 31 | f, _ := parser.ParseFile(fset, "", rawCode, parser.ParseComments) 32 | var result bool = true 33 | 34 | // search 35 | ast.Inspect(f, func(n1 ast.Node) bool { 36 | // Try to find a block statement 37 | if ret, ok := n1.(*ast.BlockStmt); ok { 38 | // filter the block 39 | if fset.Position(ret.Lbrace).Line <= ec.LineNo && fset.Position(ret.Rbrace).Line >= ec.LineNo { 40 | ast.Inspect(f, func(n2 ast.Node) bool { 41 | if ret2, ok := n2.(*ast.GoStmt); ok { 42 | ret3 := ret2.Call 43 | if fset.Position(ret3.Lparen).Line <= ec.LineNo && fset.Position(ret3.Rparen).Line >= ec.LineNo { 44 | result = false 45 | return false 46 | } 47 | } 48 | return true 49 | }) 50 | } 51 | return true 52 | } 53 | return true 54 | }) 55 | if result { 56 | log.Printf("AST-based verification: \033[32;1mPASSED\033[0m") 57 | } else { 58 | log.Printf("AST-based verification: \033[31;1mFAILED\033[0m. Starting LLVMIR-based verification") 59 | } 60 | return result 61 | } 62 | 63 | func checkLLVMIR(filePath string, ec util.EscapeCase) bool { 64 | var result bool = false 65 | // Run Go2IR to generate LLVM IR 66 | log.Println("[1] Cleaning caches ......") 67 | script := "rm -rf ~/.cache/go-build" 68 | util.ExecShell(script) 69 | log.Println("[2] Generating LLVM IR ......") 70 | script = fmt.Sprintf("Go2IR -p %s -o /tmp/goescape-cache/", filepath.Dir(filePath)) 71 | util.ExecShell(script) 72 | log.Println("[3] Analyzing ......") 73 | result = irHandler("/tmp/goescape-cache/1.ll", filePath, ec) 74 | 75 | // return result 76 | if result { 77 | log.Printf("LLVMIR-based verification: \033[32;1mPASSED\033[0m.") 78 | log.Printf(greenArrow + " Optimization in this case is \033[32;1mVALID\033[0m.") 79 | } else { 80 | log.Printf("LLVMIR-based verification: \033[31;1mFAILED\033[0m.") 81 | log.Printf(greenArrow + " Optimization in this case is \033[31;1mINVALID\033[0m.") 82 | } 83 | return false 84 | } 85 | --------------------------------------------------------------------------------