diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ade8c371d10..b41245b441f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -190,15 +190,6 @@ updates: schedule: interval: weekly day: sunday - - package-ecosystem: gomod - directory: /instrgen/driver/testdata/interface - labels: - - dependencies - - go - - Skip Changelog - schedule: - interval: weekly - day: sunday - package-ecosystem: gomod directory: /instrumentation/github.com/aws/aws-lambda-go/otellambda labels: diff --git a/instrgen/README.md b/instrgen/README.md index c40627a57c8..5554b276dd4 100644 --- a/instrgen/README.md +++ b/instrgen/README.md @@ -7,31 +7,62 @@ If you are looking for more details about internal working, see [How it works](. :construction: This package is currently work in progress. -## How to use it +## Build -In order to instrument your project you have to add following call in your entry point function, usually main -(you can look at testdata directory for reference) and invoke instrgen tool. +From driver directory execute: ``` -func main() { - rtlib.AutotelEntryPoint() +go build ``` -Instrgen requires three parameters: command, path to project and package(s) pattern we -would like to instrument. +## Prerequisites + +`instrgen` driver utility needs to be on your PATH environment variable. + +## How to use it + +Instrgen has to be invoked from main module directory and +requires three parameters: command, directory (files from specified directory will be rewritten). ``` -./instrgen --inject [path to your go project] [package(s) pattern] +./driver --inject [file pattern] [replace input source] [entry point] ``` Below concrete example with one of test instrumentation that is part of the project. ``` -./instrgen --inject ./testdata/basic ./... +driver --inject /testdata/basic yes main.main +``` + +Above command will invoke golang compiler under the hood: + ``` +go build -work -a -toolexec driver +``` + +which means that the above command can be executed directly, however first `instrgen_cmd.json` +configuration file needs to be provided. This file is created internally by `driver` based on provided +command line. + +Below example content of `instrgen_cmd.json`: + +``` +{ +"ProjectPath": ".", +"FilePattern": "/testdata/basic", +"Cmd": "inject", +"Replace": "yes", +"EntryPoint": { + "Pkg": "main", + "FunName": "main" + } +} +``` + +### Work in progress: -```./...``` works like wildcard in this case and it will instrument all packages in this path, but it can be invoked with -specific package as well. +Library instrumentation: +- HTTP ### Compatibility diff --git a/instrgen/docs/flow.png b/instrgen/docs/flow.png deleted file mode 100644 index 3f519e41f70..00000000000 Binary files a/instrgen/docs/flow.png and /dev/null differ diff --git a/instrgen/docs/how-it-works.md b/instrgen/docs/how-it-works.md index f59475d7dfa..a178bab76dc 100644 --- a/instrgen/docs/how-it-works.md +++ b/instrgen/docs/how-it-works.md @@ -3,10 +3,9 @@ `instrgen` adds OpenTelemetry instrumentation to source code by directly modifying it. It uses the AST (Abstract Syntax Tree) representation of the code to determine its operational flow and injects necessary OpenTelemetry functionality into the AST. +`instrgen` utilizes toolexec golang compiler switch. It means that it has access to all files +that takes part in the compilation process. + The AST modification algorithm is the following: -1. Search for the entry point: a function definition with `AutotelEntryPoint()`. -2. Build the call graph. Traverse all calls from the entry point through all function definitions. -3. Inject OpenTelemetry instrumentation into functions bodies. -4. Context propagation. Adding an additional context parameter to all function declarations and function call expressions that are visible - (it will not add a context argument to call expressions if they are not reachable from the entry point). -![image info](./flow.png) \ No newline at end of file +1. Rewrites go runtime package in order to provide correct context propagation. +2. Inject OpenTelemetry instrumentation into functions bodies. diff --git a/instrgen/driver/go.mod b/instrgen/driver/go.mod index ccb5b6981cc..18087e856cd 100644 --- a/instrgen/driver/go.mod +++ b/instrgen/driver/go.mod @@ -7,13 +7,12 @@ replace go.opentelemetry.io/contrib/instrgen => ../ require ( github.com/stretchr/testify v1.8.4 go.opentelemetry.io/contrib/instrgen v0.0.0-00010101000000-000000000000 + golang.org/x/tools v0.14.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect - golang.org/x/tools v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrgen/driver/go.sum b/instrgen/driver/go.sum index 454d6f43e67..e19b64f071e 100644 --- a/instrgen/driver/go.sum +++ b/instrgen/driver/go.sum @@ -5,8 +5,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= diff --git a/instrgen/driver/instrgen_test.go b/instrgen/driver/instrgen_test.go index 10674e237bb..031cbce61ad 100644 --- a/instrgen/driver/instrgen_test.go +++ b/instrgen/driver/instrgen_test.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "os" "path/filepath" @@ -27,70 +28,56 @@ import ( "github.com/stretchr/testify/require" alib "go.opentelemetry.io/contrib/instrgen/lib" + "go.opentelemetry.io/contrib/instrgen/rewriters" ) var testcases = map[string]string{ - "./testdata/basic": "./testdata/expected/basic", - "./testdata/selector": "./testdata/expected/selector", - "./testdata/interface": "./testdata/expected/interface", + "testdata/basic": "testdata/expected/basic", + "testdata/interface": "testdata/expected/interface", } var failures []string -func inject(t *testing.T, root string, packagePattern string) { - err := executeCommand("--inject-dump-ir", root, packagePattern) - require.NoError(t, err) +func TestCommand(t *testing.T) { + executor := &NullExecutor{} + err := executeCommand("--unknown", "./testdata/basic", "testdata/basic", "yes", "main.main", executor) + assert.Error(t, err) } -func TestCommands(t *testing.T) { - err := executeCommand("--dumpcfg", "./testdata/dummy", "./...") - require.NoError(t, err) - err = executeCommand("--rootfunctions", "./testdata/dummy", "./...") - require.NoError(t, err) - err = executeCommand("--prune", "./testdata/dummy", "./...") - require.NoError(t, err) - err = executeCommand("--inject", "./testdata/dummy", "./...") - require.NoError(t, err) - err = usage() - require.NoError(t, err) -} - -func TestCallGraph(t *testing.T) { - cg := makeCallGraph("./testdata/dummy", "./...") - dumpCallGraph(cg) - assert.Equal(t, len(cg), 0, "callgraph should contain 0 elems") - rf := makeRootFunctions("./testdata/dummy", "./...") - dumpRootFunctions(rf) - assert.Equal(t, len(rf), 0, "rootfunctions set should be empty") -} +func TestInstrumentation(t *testing.T) { + cwd, _ := os.Getwd() + var args []string + for k := range testcases { + filePaths := make(map[string]int) -func TestArgs(t *testing.T) { - err := checkArgs(nil) - require.Error(t, err) - args := []string{"driver", "--inject", "", "./..."} - err = checkArgs(args) - require.NoError(t, err) -} + files := alib.SearchFiles(k, ".go") + for index, file := range files { + filePaths[file] = index + } + pruner := rewriters.OtelPruner{ + FilePattern: k, Replace: true} + analyzePackage(pruner, "main", filePaths, nil, "", args) -func TestUnknownCommand(t *testing.T) { - err := executeCommand("unknown", "a", "b") - require.Error(t, err) -} + rewriter := rewriters.BasicRewriter{ + FilePattern: k, Replace: "yes", Pkg: "main", Fun: "main"} + analyzePackage(rewriter, "main", filePaths, nil, "", args) + } + fmt.Println(cwd) -func TestInstrumentation(t *testing.T) { for k, v := range testcases { - inject(t, k, "./...") - files := alib.SearchFiles(k, ".go_pass_tracing") - expectedFiles := alib.SearchFiles(v, ".go") + files := alib.SearchFiles(cwd+"/"+k, ".go") + expectedFiles := alib.SearchFiles(cwd+"/"+v, ".go") numOfFiles := len(expectedFiles) fmt.Println("Go Files:", len(files)) fmt.Println("Expected Go Files:", len(expectedFiles)) + assert.True(t, len(files) > 0) numOfComparisons := 0 for _, file := range files { fmt.Println(filepath.Base(file)) for _, expectedFile := range expectedFiles { fmt.Println(filepath.Base(expectedFile)) - if filepath.Base(file) == filepath.Base(expectedFile+"_pass_tracing") { + if filepath.Base(file) == filepath.Base(expectedFile) { + fmt.Println(file, " : ", expectedFile) f1, err1 := os.ReadFile(file) require.NoError(t, err1) f2, err2 := os.ReadFile(expectedFile) @@ -106,12 +93,120 @@ func TestInstrumentation(t *testing.T) { fmt.Println("numberOfComparisons:", numOfComparisons) panic("not all files were compared") } - _, err := Prune(k, "./...", false) - if err != nil { - fmt.Println("Prune failed") - } } - for _, f := range failures { - fmt.Println("FAILURE : ", f) +} + +type NullExecutor struct { +} + +func (executor *NullExecutor) Execute(_ string, _ []string) { +} + +func (executor *NullExecutor) Run() error { + return nil +} + +func TestToolExecMain(t *testing.T) { + for k := range testcases { + var args []string + files := alib.SearchFiles(k, ".go") + args = append(args, []string{"-o", "/tmp/go-build", "-p", "main", "-pack", "-asmhdr", "go_asm.h"}...) + args = append(args, files...) + instrgenCfg := InstrgenCmd{FilePattern: k, Cmd: "prune", Replace: "yes", + EntryPoint: EntryPoint{Pkg: "main", FunName: "main"}} + rewriterS := makeRewriters(instrgenCfg) + analyze(args, rewriterS) + instrgenCfg.Cmd = "inject" + rewriterS = makeRewriters(instrgenCfg) + analyze(args, rewriterS) + } + for k := range testcases { + var args []string + files := alib.SearchFiles(k, ".go") + args = append(args, []string{"-pack", "-asmhdr", "go_asm.h"}...) + args = append(args, files...) + instrgenCfg := InstrgenCmd{FilePattern: k, Cmd: "prune", Replace: "no", + EntryPoint: EntryPoint{Pkg: "main", FunName: "main"}} + rewriterS := makeRewriters(instrgenCfg) + analyze(args, rewriterS) + instrgenCfg.Cmd = "inject" + rewriterS = makeRewriters(instrgenCfg) + analyze(args, rewriterS) + } + for k := range testcases { + instrgenCfg := InstrgenCmd{FilePattern: k, Cmd: "prune", Replace: "yes", + EntryPoint: EntryPoint{Pkg: "main", FunName: "main"}} + rewriterS := makeRewriters(instrgenCfg) + var args []string + executor := &NullExecutor{} + err := toolExecMain(args, rewriterS, executor) + assert.Error(t, err) + } +} + +func TestGetCommandName(t *testing.T) { + cmd := GetCommandName([]string{"/usr/local/go/compile"}) + assert.True(t, cmd == "compile") + cmd = GetCommandName([]string{"/usr/local/go/compile.exe"}) + assert.True(t, cmd == "compile") + cmd = GetCommandName([]string{}) + assert.True(t, cmd == "") +} + +func TestExecutePass(t *testing.T) { + executor := &ToolExecutor{} + require.NoError(t, executePass([]string{"go", "version"}, executor)) +} + +func TestDriverMain(t *testing.T) { + executor := &NullExecutor{} + { + err := os.Remove("instrgen_cmd.json") + _ = err + var args []string + args = append(args, "compile") + err = driverMain(args, executor) + require.Error(t, err) + } + for k := range testcases { + var args []string + files := alib.SearchFiles(k, ".go") + args = append(args, []string{"-o", "/tmp/go-build", "-p", "main", "-pack", "-asmhdr", "go_asm.h"}...) + args = append(args, files...) + instrgenCfg := InstrgenCmd{FilePattern: k, Cmd: "prune", Replace: "yes", + EntryPoint: EntryPoint{Pkg: "main", FunName: "main"}} + err := driverMain(args, executor) + assert.NoError(t, err) + instrgenCfg.Cmd = "inject" + err = driverMain(args, executor) + assert.NoError(t, err) + } + { + var args []string + args = append(args, "compile") + instrgenCfg := InstrgenCmd{FilePattern: "/testdata/basic", Cmd: "inject", Replace: "yes", + EntryPoint: EntryPoint{Pkg: "main", FunName: "main"}} + file, _ := json.MarshalIndent(instrgenCfg, "", " ") + err := os.WriteFile("instrgen_cmd.json", file, 0644) + require.NoError(t, err) + err = driverMain(args, executor) + require.NoError(t, err) + } + for k := range testcases { + var args []string + args = append(args, []string{"--inject", k, "yes", "main.main"}...) + err := driverMain(args, executor) + assert.NoError(t, err) + } + { + var args []string + args = append(args, "--inject") + err := driverMain(args, executor) + assert.Error(t, err) + } + { + var args []string + err := driverMain(args, executor) + assert.NoError(t, err) } } diff --git a/instrgen/driver/main.go b/instrgen/driver/main.go index 55498a99a68..32106d453c1 100644 --- a/instrgen/driver/main.go +++ b/instrgen/driver/main.go @@ -15,86 +15,82 @@ package main import ( + "encoding/json" "errors" "fmt" "go/ast" - "log" + "go/build" + "go/parser" + "go/printer" + "go/token" + "go/types" + "golang.org/x/tools/go/loader" "os" + "os/exec" + "path/filepath" + "strings" + "sync" alib "go.opentelemetry.io/contrib/instrgen/lib" + "go.opentelemetry.io/contrib/instrgen/rewriters" ) -func usage() error { - fmt.Println("\nusage driver --command [path to go project] [package pattern]") - fmt.Println("\tcommand:") - fmt.Println("\t\tinject (injects open telemetry calls into project code)") - fmt.Println("\t\tinject-dump-ir (injects open telemetry calls into project code and intermediate passes)") - fmt.Println("\t\tprune (prune open telemetry calls") - fmt.Println("\t\tdumpcfg (dumps control flow graph)") - fmt.Println("\t\trootfunctions (dumps root functions)") - return nil -} - -func makeAnalysis(projectPath string, packagePattern string, debug bool) *alib.PackageAnalysis { - var rootFunctions []alib.FuncDescriptor +const ( + InfoColor = "\033[1;34m%s\033[0m" + NoticeColor = "\033[1;36m%s\033[0m" + WarningColor = "\033[1;33m%s\033[0m" + ErrorColor = "\033[1;31m%s\033[0m" + DebugColor = "\033[0;36m%s\033[0m" +) - interfaces := alib.FindInterfaces(projectPath, packagePattern) - rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) - funcDecls := alib.FindFuncDecls(projectPath, packagePattern, interfaces) - backwardCallGraph := alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) - fmt.Println("\n\tchild parent") - for k, v := range backwardCallGraph { - fmt.Print("\n\t", k) - fmt.Print(" ", v) - } - fmt.Println("") - analysis := &alib.PackageAnalysis{ - ProjectPath: projectPath, - PackagePattern: packagePattern, - RootFunctions: rootFunctions, - FuncDecls: funcDecls, - Callgraph: backwardCallGraph, - Interfaces: interfaces, - Debug: debug, - } - return analysis +func usage() { + fmt.Printf(InfoColor, "\nusage driver --command [file pattern] replace entrypoint") + fmt.Println() + fmt.Printf(InfoColor, "\tcommand:") + fmt.Println() + fmt.Printf(InfoColor, "\t\tinject (injects open telemetry calls into project code)") + fmt.Println() + fmt.Printf(InfoColor, "\t\tprune (prune open telemetry calls") + fmt.Println() } -// Prune. -func Prune(projectPath string, packagePattern string, debug bool) ([]*ast.File, error) { - analysis := makeAnalysis(projectPath, packagePattern, debug) - return analysis.Execute(&alib.OtelPruner{}, otelPrunerPassSuffix) +// Entry point function. +type EntryPoint struct { + Pkg string + FunName string } -func makeCallGraph(projectPath string, packagePattern string) map[alib.FuncDescriptor][]alib.FuncDescriptor { - var funcDecls map[alib.FuncDescriptor]bool - var backwardCallGraph map[alib.FuncDescriptor][]alib.FuncDescriptor +// Command passed to the compiler toolchain. +type InstrgenCmd struct { + ProjectPath string + FilePattern string + Cmd string + Replace string + EntryPoint EntryPoint +} - interfaces := alib.FindInterfaces(projectPath, packagePattern) - funcDecls = alib.FindFuncDecls(projectPath, packagePattern, interfaces) - backwardCallGraph = alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) - return backwardCallGraph +// CommandExecutor. +type CommandExecutor interface { + Execute(cmd string, args []string) + Run() error } -func makeRootFunctions(projectPath string, packagePattern string) []alib.FuncDescriptor { - var rootFunctions []alib.FuncDescriptor - rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) - return rootFunctions +// ToolExecutor. +type ToolExecutor struct { + cmd *exec.Cmd } -func dumpCallGraph(callGraph map[alib.FuncDescriptor][]alib.FuncDescriptor) { - fmt.Println("\n\tchild parent") - for k, v := range callGraph { - fmt.Print("\n\t", k) - fmt.Print(" ", v) - } +// Wraps Execute. +func (executor *ToolExecutor) Execute(cmd string, args []string) { + executor.cmd = exec.Command(cmd, args...) + executor.cmd.Stdin = os.Stdin + executor.cmd.Stdout = os.Stdout + executor.cmd.Stderr = os.Stderr } -func dumpRootFunctions(rootFunctions []alib.FuncDescriptor) { - fmt.Println("rootfunctions:") - for _, fun := range rootFunctions { - fmt.Println("\t" + fun.TypeHash()) - } +// Wraps Run. +func (executor *ToolExecutor) Run() error { + return executor.cmd.Run() } func isDirectory(path string) (bool, error) { @@ -106,55 +102,62 @@ func isDirectory(path string) (bool, error) { return fileInfo.IsDir(), err } -// Parsing algorithm works as follows. It goes through all function -// decls and infer function bodies to find call to AutotelEntryPoint -// A parent function of this call will become root of instrumentation -// Each function call from this place will be instrumented automatically. -func executeCommand(command string, projectPath string, packagePattern string) error { +func LoadProgram(projectPath string, ginfo *types.Info) (*loader.Program, error) { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + conf := loader.Config{ParserMode: parser.ParseComments} + conf.Build = &build.Default + conf.Build.CgoEnabled = false + conf.Build.Dir = filepath.Join(cwd, projectPath) + conf.Import(projectPath) + var mutex = &sync.RWMutex{} + conf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { + for k, v := range info.Defs { + mutex.Lock() + ginfo.Defs[k] = v + mutex.Unlock() + } + for k, v := range info.Uses { + mutex.Lock() + ginfo.Uses[k] = v + mutex.Unlock() + } + for k, v := range info.Selections { + mutex.Lock() + ginfo.Selections[k] = v + mutex.Unlock() + } + } + return conf.Load() +} + +func executeCommand(command string, projectPath string, packagePattern string, replaceSource string, entryPoint string, executor CommandExecutor) error { isDir, err := isDirectory(projectPath) if !isDir { - _ = usage() return errors.New("[path to go project] argument must be directory") } if err != nil { return err } + if command == "--prune" { + replaceSource = "yes" + } + switch command { - case "--inject": - _, err := Prune(projectPath, packagePattern, false) + case "--inject", "--prune": + entry := strings.Split(entryPoint, ".") + data := InstrgenCmd{projectPath, packagePattern, command[2:], replaceSource, + EntryPoint{entry[0], entry[1]}} + file, _ := json.MarshalIndent(data, "", " ") + err := os.WriteFile("instrgen_cmd.json", file, 0644) if err != nil { return err } - analysis := makeAnalysis(projectPath, packagePattern, false) - err = ExecutePasses(analysis) - if err != nil { - return err - } - fmt.Println("\tinstrumentation done") - return nil - case "--inject-dump-ir": - _, err := Prune(projectPath, packagePattern, true) - if err != nil { - return err - } - analysis := makeAnalysis(projectPath, packagePattern, true) - err = ExecutePassesDumpIr(analysis) - if err != nil { - return err - } - fmt.Println("\tinstrumentation done") - return nil - case "--dumpcfg": - backwardCallGraph := makeCallGraph(projectPath, packagePattern) - dumpCallGraph(backwardCallGraph) - return nil - case "--rootfunctions": - rootFunctions := makeRootFunctions(projectPath, packagePattern) - dumpRootFunctions(rootFunctions) - return nil - case "--prune": - _, err := Prune(projectPath, packagePattern, false) - if err != nil { + executor.Execute("go", []string{"build", "-work", "-a", "-toolexec", "driver"}) + //fmt.Println("invoke : " + executor.cmd.String()) + if err := executor.Run(); err != nil { return err } return nil @@ -165,20 +168,294 @@ func executeCommand(command string, projectPath string, packagePattern string) e func checkArgs(args []string) error { if len(args) != 4 { - _ = usage() return errors.New("wrong arguments") } return nil } -func main() { - fmt.Println("autotel compiler") - err := checkArgs(os.Args) +func executePass(args []string, executor CommandExecutor) error { + path := args[0] + args = args[1:] + executor.Execute(path, args) + return executor.Run() +} + +// GetCommandName extracts command name from args. +func GetCommandName(args []string) string { + if len(args) == 0 { + return "" + } + + cmd := filepath.Base(args[0]) + if ext := filepath.Ext(cmd); ext != "" { + cmd = strings.TrimSuffix(cmd, ext) + } + return cmd +} + +func analyzePackage(rewriter alib.PackageRewriter, + pkg string, filePaths map[string]int, + trace *os.File, destPath string, + args []string, + remappedFilePaths map[string]string) []string { + fset := token.NewFileSet() + // TODO handle trace + _ = trace + extraFilesWritten := false + + removedFilePaths := make(map[string]int) + for filePath, index := range filePaths { + trace.WriteString(rewriter.Id() + ":" + filePath) + trace.WriteString("\n") + file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + if err != nil { + continue + } + if rewriter.Inject(pkg, filePath) { + rewriter.Rewrite(pkg, file, fset, trace) + + if rewriter.ReplaceSource(pkg, filePath) { + var out *os.File + out, err = alib.CreateFile(fset.File(file.Pos()).Name() + "tmp") + if err != nil { + continue + } + err = printer.Fprint(out, fset, file) + if err != nil { + continue + } + oldFileName := fset.File(file.Pos()).Name() + "tmp" + newFileName := fset.File(file.Pos()).Name() + err = os.Rename(oldFileName, newFileName) + if err != nil { + continue + } + } else { + filename := filepath.Base(filePath) + out, err := alib.CreateFile(destPath + "/" + filename + "tmp") + + if err != nil { + continue + } + err = printer.Fprint(out, fset, file) + if err != nil { + continue + } + oldFileName := destPath + "/" + filename + "tmp" + newFileName := destPath + "/" + filename + err = os.Rename(oldFileName, newFileName) + if err != nil { + continue + } + out.Close() + args[index] = destPath + "/" + filename + removedFilePaths[filePath] = index + remappedFilePaths[args[index]] = filePath + } + if !extraFilesWritten { + files := rewriter.WriteExtraFiles(pkg, destPath) + if len(files) > 0 { + args = append(args, files...) + } + extraFilesWritten = true + } + } + } + for k, v := range removedFilePaths { + delete(filePaths, k) + filePaths[args[v]] = v + } + return args +} + +func analyze(args []string, rewriterS []alib.PackageRewriter, remappedFilePaths map[string]string) []string { + trace, _ := os.OpenFile("args", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + argsLen := len(args) + var destPath string + var pkg string + + for i, a := range args { + // output directory + if a == "-o" { + destPath = filepath.Dir(string(args[i+1])) + } + // package + if a == "-p" { + pkg = string(args[i+1]) + } + // source files + if a == "-pack" { + files := make(map[string]int) + for j := i + 1; j < argsLen; j++ { + // omit -asmhdr switch + following header+ + if string(args[j]) == "-asmhdr" { + j = j + 2 + } + if !strings.HasSuffix(args[j], ".go") { + continue + } + filePath := args[j] + files[filePath] = j + } + for _, rewriter := range rewriterS { + args = analyzePackage(rewriter, pkg, files, trace, destPath, args, remappedFilePaths) + } + } + } + return args +} + +func toolExecMain(args []string, rewriterS []alib.PackageRewriter, executor CommandExecutor, remappedFilePaths map[string]string) error { + args = analyze(args, rewriterS, remappedFilePaths) + if len(args) == 0 { + usage() + return errors.New("wrong command") + } + + err := executePass(args[0:], executor) + if err != nil { + return err + } + return nil +} + +func printStack(stack []*ast.CallExpr) { + for len(stack) > 0 { + n := len(stack) - 1 // Top element + if sel, ok := stack[n].Fun.(*ast.SelectorExpr); ok { + if ident, ok := sel.X.(*ast.Ident); ok { + fmt.Print(ident.Name) + } + fmt.Print(".") + fmt.Print(sel.Sel.Name) + } + stack = stack[:n] // Pop + } +} + +func makeRewriters(instrgenCfg InstrgenCmd, remappedFilePaths map[string]string) []alib.PackageRewriter { + var rewriterS []alib.PackageRewriter + switch instrgenCfg.Cmd { + case "inject": + rewriterS = append(rewriterS, rewriters.RuntimeRewriter{ + FilePattern: instrgenCfg.FilePattern}) + rewriterS = append(rewriterS, rewriters.BasicRewriter{ + FilePattern: instrgenCfg.FilePattern, Replace: instrgenCfg.Replace, + Pkg: instrgenCfg.EntryPoint.Pkg, Fun: instrgenCfg.EntryPoint.FunName, RemappedFilePaths: remappedFilePaths}) + case "prune": + rewriterS = append(rewriterS, rewriters.OtelPruner{ + FilePattern: instrgenCfg.FilePattern, Replace: true}) + } + return rewriterS +} + +func goModTidy(projectPath string, replace string, prog *loader.Program, ginfo *types.Info) { + for _, pkg := range prog.AllPackages { + if len(pkg.Files) > 0 { + path := prog.Fset.File(pkg.Files[0].Pos()).Name() + if !strings.Contains(path, projectPath) { + continue + } + if !alib.FileExists(filepath.Dir(path) + "/instrgen_imports.go") { + f, err := alib.CreateFile(filepath.Dir(path) + "/instrgen_imports.go") + if err != nil { + fmt.Println(err) + return + } + imports := + `package ` + pkg.Pkg.Name() + ` +import ( + _ "go.opentelemetry.io/contrib/instrgen/rtlib" + _ "go.opentelemetry.io/otel" + _ "context" + _ "runtime" + _ "go.opentelemetry.io/otel/trace" + _ "go.opentelemetry.io/otel/sdk/trace" +) +` + _, err = f.WriteString(imports) + if err != nil { + fmt.Println(err) + return + } + } + } + } + executor := &ToolExecutor{} + executor.Execute("go", []string{"mod", "tidy"}) + fmt.Printf(InfoColor, "invoke : "+executor.cmd.String()+"\n") + if err := executor.Run(); err != nil { + fmt.Println(err) + } +} + +func driverMain(args []string, executor CommandExecutor) error { + cmdName := GetCommandName(args) + if cmdName != "compile" { + // do semantic check before injecting + if cmdName == "--inject" { + ginfo := &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + } + fmt.Printf(InfoColor, "instrgen semantic analysis...\n") + prog, err := LoadProgram(".", ginfo) + if err != nil { + err = errors.New("Load failed : " + err.Error()) + return err + } + goModTidy(args[1], args[2], prog, ginfo) + } + switch cmdName { + case "--inject", "--prune": + fmt.Printf(InfoColor, "instrgen compiler\n") + err := checkArgs(args) + if err != nil { + usage() + return err + } + replace := "no" + if len(args) > 2 { + replace = args[2] + } + err = executeCommand(args[0], ".", args[1], replace, args[3], executor) + if err != nil { + return err + } + return nil + } + if len(args) > 0 { + err := executePass(args[0:], executor) + if err != nil { + return err + } + } else { + usage() + } + return nil + } + content, err := os.ReadFile("./instrgen_cmd.json") if err != nil { - return + return err } - err = executeCommand(os.Args[1], os.Args[2], os.Args[3]) + + var instrgenCfg InstrgenCmd + err = json.Unmarshal(content, &instrgenCfg) + if err != nil { + return err + } + remappedFilePaths := make(map[string]string) + rewriterS := makeRewriters(instrgenCfg, remappedFilePaths) + return toolExecMain(args, rewriterS, executor, remappedFilePaths) +} + +func main() { + executor := &ToolExecutor{} + err := driverMain(os.Args[1:], executor) if err != nil { - log.Fatal(err) + fmt.Println() + fmt.Printf(ErrorColor, err.Error()) + fmt.Println() } } diff --git a/instrgen/driver/passes.go b/instrgen/driver/passes.go deleted file mode 100644 index 6c3f81f2bcf..00000000000 --- a/instrgen/driver/passes.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - - "go.opentelemetry.io/contrib/instrgen/lib" -) - -const ( - otelPrunerPassSuffix = "_pass_pruner" - contextPassFileSuffix = "_pass_ctx" - instrumentationPassFileSuffix = "_pass_tracing" -) - -// ExecutePassesDumpIr. -func ExecutePassesDumpIr(analysis *lib.PackageAnalysis) error { - fmt.Println("Instrumentation") - _, err := analysis.Execute(&lib.InstrumentationPass{}, "") - if err != nil { - return err - } - - fmt.Println("ContextPropagation") - _, err = analysis.Execute(&lib.ContextPropagationPass{}, instrumentationPassFileSuffix) - return err -} - -// ExecutePasses. -func ExecutePasses(analysis *lib.PackageAnalysis) error { - fmt.Println("Instrumentation") - _, err := analysis.Execute(&lib.InstrumentationPass{}, instrumentationPassFileSuffix) - if err != nil { - return err - } - fmt.Println("ContextPropagation") - _, err = analysis.Execute(&lib.ContextPropagationPass{}, contextPassFileSuffix) - return err -} diff --git a/instrgen/driver/testdata/basic/fib.go b/instrgen/driver/testdata/basic/fib.go index 343f04a0988..88217693e78 100644 --- a/instrgen/driver/testdata/basic/fib.go +++ b/instrgen/driver/testdata/basic/fib.go @@ -17,6 +17,8 @@ package main import ( "fmt" + _ "go.opentelemetry.io/otel" + _ "context" ) func foo() { diff --git a/instrgen/driver/testdata/basic/goroutines.go b/instrgen/driver/testdata/basic/goroutines.go index 4dc896692ab..504b46844b0 100644 --- a/instrgen/driver/testdata/basic/goroutines.go +++ b/instrgen/driver/testdata/basic/goroutines.go @@ -17,6 +17,8 @@ package main import ( "fmt" + _ "go.opentelemetry.io/otel" + _ "context" ) func goroutines() { diff --git a/instrgen/driver/testdata/basic/main.go b/instrgen/driver/testdata/basic/main.go index edcb0b5fcf3..a06e6a5ae1c 100644 --- a/instrgen/driver/testdata/basic/main.go +++ b/instrgen/driver/testdata/basic/main.go @@ -17,8 +17,9 @@ package main import ( "fmt" - "go.opentelemetry.io/contrib/instrgen/rtlib" + _ "go.opentelemetry.io/otel" + _ "context" ) func recur(n int) { diff --git a/instrgen/driver/testdata/basic/methods.go b/instrgen/driver/testdata/basic/methods.go index c0afb1c5b85..94f829667d0 100644 --- a/instrgen/driver/testdata/basic/methods.go +++ b/instrgen/driver/testdata/basic/methods.go @@ -15,6 +15,11 @@ //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main +import ( + _ "go.opentelemetry.io/otel" + _ "context" +) + type element struct { } @@ -35,6 +40,7 @@ func (i impl) anotherfoo(p int) int { } func anotherfoo(p int) int { + return 1 } @@ -54,4 +60,5 @@ func methods() { var in i in = impl{} in.anotherfoo(10) + anotherfoo(5) } diff --git a/instrgen/driver/testdata/basic/package.go b/instrgen/driver/testdata/basic/package.go index 09e736ba9a8..73c22b506ff 100644 --- a/instrgen/driver/testdata/basic/package.go +++ b/instrgen/driver/testdata/basic/package.go @@ -17,9 +17,12 @@ package main import ( "os" + _ "go.opentelemetry.io/otel" + _ "context" ) func Close() error { + return nil } diff --git a/instrgen/driver/testdata/expected/basic/fib.go b/instrgen/driver/testdata/expected/basic/fib.go index fea3dd6fb36..7493e5f04c3 100644 --- a/instrgen/driver/testdata/expected/basic/fib.go +++ b/instrgen/driver/testdata/expected/basic/fib.go @@ -17,34 +17,44 @@ package main import ( "fmt" + __atel_runtime "runtime" __atel_context "context" + _ "go.opentelemetry.io/otel" __atel_otel "go.opentelemetry.io/otel" + _ "context" ) -func foo(__atel_tracing_ctx __atel_context.Context,) { +func foo() { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("foo").Start(__atel_tracing_ctx, "foo") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + fmt.Println("foo") } -func FibonacciHelper(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { +func FibonacciHelper(n uint) (uint64, error) { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("FibonacciHelper").Start(__atel_tracing_ctx, "FibonacciHelper") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + func() { - __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") - _ = __atel_child_tracing_ctx - defer __atel_span.End() - foo(__atel_child_tracing_ctx) + + foo() }() - return Fibonacci(__atel_child_tracing_ctx, n) + return Fibonacci(n) } -func Fibonacci(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { +func Fibonacci(n uint) (uint64, error) { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Fibonacci").Start(__atel_tracing_ctx, "Fibonacci") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + if n <= 1 { return uint64(n), nil } diff --git a/instrgen/driver/testdata/expected/basic/goroutines.go b/instrgen/driver/testdata/expected/basic/goroutines.go index 69ef766b180..36c7e99a39d 100644 --- a/instrgen/driver/testdata/expected/basic/goroutines.go +++ b/instrgen/driver/testdata/expected/basic/goroutines.go @@ -17,20 +17,24 @@ package main import ( "fmt" + __atel_runtime "runtime" __atel_context "context" + _ "go.opentelemetry.io/otel" __atel_otel "go.opentelemetry.io/otel" + _ "context" ) -func goroutines(__atel_tracing_ctx __atel_context.Context,) { +func goroutines() { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("goroutines").Start(__atel_tracing_ctx, "goroutines") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + messages := make(chan string) go func() { - __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") - _ = __atel_child_tracing_ctx - defer __atel_span.End() + messages <- "ping" }() diff --git a/instrgen/driver/testdata/expected/basic/main.go b/instrgen/driver/testdata/expected/basic/main.go index 5e7459b9670..eaf11cf6fe4 100644 --- a/instrgen/driver/testdata/expected/basic/main.go +++ b/instrgen/driver/testdata/expected/basic/main.go @@ -17,18 +17,23 @@ package main import ( "fmt" + __atel_runtime "runtime" __atel_context "context" - "go.opentelemetry.io/contrib/instrgen/rtlib" __atel_otel "go.opentelemetry.io/otel" + _ "go.opentelemetry.io/otel" + _ "context" ) -func recur(__atel_tracing_ctx __atel_context.Context, n int) { +func recur(n int) { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("recur").Start(__atel_tracing_ctx, "recur") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + if n > 0 { - recur(__atel_child_tracing_ctx, n-1) + recur(n - 1) } } @@ -40,10 +45,12 @@ func main() { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) + rtlib.AutotelEntryPoint() - fmt.Println(FibonacciHelper(__atel_child_tracing_ctx, 10)) - recur(__atel_child_tracing_ctx, 5) - goroutines(__atel_child_tracing_ctx) - pack(__atel_child_tracing_ctx) - methods(__atel_child_tracing_ctx) + fmt.Println(FibonacciHelper(10)) + recur(5) + goroutines() + pack() + methods() } diff --git a/instrgen/driver/testdata/expected/basic/methods.go b/instrgen/driver/testdata/expected/basic/methods.go index 9bd7ba4c5cf..6d2e3df2c9e 100644 --- a/instrgen/driver/testdata/expected/basic/methods.go +++ b/instrgen/driver/testdata/expected/basic/methods.go @@ -16,8 +16,11 @@ package main import ( - __atel_context "context" + _ "go.opentelemetry.io/otel" + __atel_runtime "runtime" __atel_otel "go.opentelemetry.io/otel" + __atel_context "context" + _ "context" ) type element struct { @@ -28,46 +31,62 @@ type driver struct { } type i interface { - anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int + anotherfoo(p int) int } type impl struct { } -func (i impl) anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int { +func (i impl) anotherfoo(p int) int { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anotherfoo").Start(__atel_tracing_ctx, "anotherfoo") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + return 5 } func anotherfoo(p int) int { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anotherfoo").Start(__atel_tracing_ctx, "anotherfoo") + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) + defer __atel_span.End() + return 1 } -func (d driver) process(__atel_tracing_ctx __atel_context.Context, a int) { +func (d driver) process(a int) { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("process").Start(__atel_tracing_ctx, "process") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() } -func (e element) get(__atel_tracing_ctx __atel_context.Context, a int) { +func (e element) get(a int) { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("get").Start(__atel_tracing_ctx, "get") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() } -func methods(__atel_tracing_ctx __atel_context.Context,) { +func methods() { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("methods").Start(__atel_tracing_ctx, "methods") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() d := driver{} - d.process(__atel_child_tracing_ctx, 10) - d.e.get(__atel_child_tracing_ctx, 5) + d.process(10) + d.e.get(5) var in i in = impl{} - in.anotherfoo(__atel_child_tracing_ctx, 10) + in.anotherfoo(10) + anotherfoo(5) } diff --git a/instrgen/driver/testdata/expected/basic/package.go b/instrgen/driver/testdata/expected/basic/package.go index 339372a3c33..4ccca84ff3e 100644 --- a/instrgen/driver/testdata/expected/basic/package.go +++ b/instrgen/driver/testdata/expected/basic/package.go @@ -17,18 +17,30 @@ package main import ( "os" + __atel_runtime "runtime" __atel_context "context" + _ "go.opentelemetry.io/otel" __atel_otel "go.opentelemetry.io/otel" + _ "context" ) func Close() error { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Close").Start(__atel_tracing_ctx, "Close") + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) + defer __atel_span.End() + return nil } -func pack(__atel_tracing_ctx __atel_context.Context,) { +func pack() { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("pack").Start(__atel_tracing_ctx, "pack") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() + f, e := os.Create("temp") defer f.Close() if e != nil { diff --git a/instrgen/driver/testdata/expected/interface/app/impl.go b/instrgen/driver/testdata/expected/interface/app/impl.go index ec59c51ab7e..6c07516986c 100644 --- a/instrgen/driver/testdata/expected/interface/app/impl.go +++ b/instrgen/driver/testdata/expected/interface/app/impl.go @@ -17,6 +17,7 @@ package app import ( "fmt" + __atel_runtime "runtime" __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) @@ -24,9 +25,11 @@ import ( type BasicSerializer struct { } -func (b BasicSerializer) Serialize(__atel_tracing_ctx __atel_context.Context,) { +func (b BasicSerializer) Serialize() { + __atel_tracing_ctx := __atel_runtime.InstrgenGetTls().(__atel_context.Context) + defer __atel_runtime.InstrgenSetTls(__atel_tracing_ctx) __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Serialize").Start(__atel_tracing_ctx, "Serialize") - _ = __atel_child_tracing_ctx + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) defer __atel_span.End() fmt.Println("Serialize") diff --git a/instrgen/driver/testdata/expected/interface/main.go b/instrgen/driver/testdata/expected/interface/main.go index 4a7870eccc1..433055f032c 100644 --- a/instrgen/driver/testdata/expected/interface/main.go +++ b/instrgen/driver/testdata/expected/interface/main.go @@ -17,6 +17,7 @@ package main import ( . "go.opentelemetry.io/contrib/instrgen/testdata/interface/app" + __atel_runtime "runtime" __atel_otel "go.opentelemetry.io/otel" __atel_context "context" . "go.opentelemetry.io/contrib/instrgen/testdata/interface/serializer" @@ -31,10 +32,11 @@ func main() { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() + __atel_runtime.InstrgenSetTls(__atel_child_tracing_ctx) rtlib.AutotelEntryPoint() bs := BasicSerializer{} var s Serializer s = bs - s.Serialize(__atel_child_tracing_ctx) + s.Serialize() } diff --git a/instrgen/driver/testdata/expected/interface/serializer/interface.go b/instrgen/driver/testdata/expected/interface/serializer/interface.go index 9f258907ff3..e7e81f9aa67 100644 --- a/instrgen/driver/testdata/expected/interface/serializer/interface.go +++ b/instrgen/driver/testdata/expected/interface/serializer/interface.go @@ -15,8 +15,6 @@ //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package serializer -import __atel_context "context" - type Serializer interface { - Serialize(__atel_tracing_ctx __atel_context.Context,) + Serialize() } diff --git a/instrgen/driver/testdata/expected/selector/main.go b/instrgen/driver/testdata/expected/selector/main.go index f743f27b3fa..a064963a1ab 100644 --- a/instrgen/driver/testdata/expected/selector/main.go +++ b/instrgen/driver/testdata/expected/selector/main.go @@ -32,6 +32,7 @@ func (impl Impl) Foo(__atel_tracing_ctx __atel_context.Context, i int) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Foo").Start(__atel_tracing_ctx, "Foo") _ = __atel_child_tracing_ctx defer __atel_span.End() + } func main() { @@ -42,6 +43,7 @@ func main() { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() + rtlib.AutotelEntryPoint() a := []Driver{ Impl{}, diff --git a/instrgen/driver/testdata/interface/go.mod b/instrgen/driver/testdata/interface/go.mod deleted file mode 100644 index 07ea126be2e..00000000000 --- a/instrgen/driver/testdata/interface/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module go.opentelemetry.io/contrib/instrgen/testdata/interface - -go 1.20 - -replace go.opentelemetry.io/contrib/instrgen => ../../.. - -require go.opentelemetry.io/contrib/instrgen v0.0.0-20221228173227-92e0588b124b - -require ( - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect - golang.org/x/sys v0.13.0 // indirect -) diff --git a/instrgen/driver/testdata/interface/go.sum b/instrgen/driver/testdata/interface/go.sum deleted file mode 100644 index 28a43a08053..00000000000 --- a/instrgen/driver/testdata/interface/go.sum +++ /dev/null @@ -1,22 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/instrgen/go.mod b/instrgen/go.mod index b383a58f617..f967d1c2933 100644 --- a/instrgen/go.mod +++ b/instrgen/go.mod @@ -14,6 +14,5 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect - golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect ) diff --git a/instrgen/go.sum b/instrgen/go.sum index e670857dbb0..ca60a4734fc 100644 --- a/instrgen/go.sum +++ b/instrgen/go.sum @@ -18,8 +18,6 @@ go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+Gf go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= diff --git a/instrgen/lib/analysis.go b/instrgen/lib/analysis.go deleted file mode 100644 index 53ecf86885c..00000000000 --- a/instrgen/lib/analysis.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib // import "go.opentelemetry.io/contrib/instrgen/lib" - -import ( - "fmt" - "go/ast" - "go/printer" - "go/token" - "os" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/packages" -) - -// PackageAnalysis analyze all package set accrding to passed -// pattern. It requires an information about path, pattern, -// root functions - entry points, function declarations, -// and so on. -type PackageAnalysis struct { - ProjectPath string - PackagePattern string - RootFunctions []FuncDescriptor - FuncDecls map[FuncDescriptor]bool - Callgraph map[FuncDescriptor][]FuncDescriptor - Interfaces map[string]bool - Debug bool -} - -type importaction int - -const ( - // const that tells whether package should be imported. - Add importaction = iota - // or removed. - Remove -) - -// Stores an information about operations on packages. -// Currently packages can be imported with an aliases -// or without. -type Import struct { - NamedPackage string - Package string - ImportAction importaction -} - -// FileAnalysisPass executes an analysis for -// specific file node - translation unit. -type FileAnalysisPass interface { - Execute(node *ast.File, - analysis *PackageAnalysis, - pkg *packages.Package, - pkgs []*packages.Package) []Import -} - -func createFile(name string) (*os.File, error) { - var out *os.File - out, err := os.Create(name) - if err != nil { - defer out.Close() - } - return out, err -} - -func addImports(imports []Import, fset *token.FileSet, fileNode *ast.File) { - for _, imp := range imports { - if imp.ImportAction == Add { - if len(imp.NamedPackage) > 0 { - astutil.AddNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) - } else { - astutil.AddImport(fset, fileNode, imp.Package) - } - } else { - if len(imp.NamedPackage) > 0 { - astutil.DeleteNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) - } else { - astutil.DeleteImport(fset, fileNode, imp.Package) - } - } - } -} - -// Execute function, main entry point to analysis process. -func (analysis *PackageAnalysis) Execute(pass FileAnalysisPass, fileSuffix string) ([]*ast.File, error) { - fset := token.NewFileSet() - cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: analysis.ProjectPath} - pkgs, err := packages.Load(cfg, analysis.PackagePattern) - if err != nil { - return nil, err - } - var fileNodeSet []*ast.File - for _, pkg := range pkgs { - fmt.Println("\t", pkg) - // fileNode represents a translationUnit - var fileNode *ast.File - for _, fileNode = range pkg.Syntax { - fmt.Println("\t\t", fset.File(fileNode.Pos()).Name()) - var out *os.File - out, err = createFile(fset.File(fileNode.Pos()).Name() + fileSuffix) - if err != nil { - return nil, err - } - if len(analysis.RootFunctions) == 0 { - e := printer.Fprint(out, fset, fileNode) - if e != nil { - return nil, e - } - continue - } - imports := pass.Execute(fileNode, analysis, pkg, pkgs) - addImports(imports, fset, fileNode) - e := printer.Fprint(out, fset, fileNode) - if e != nil { - return nil, e - } - if !analysis.Debug { - oldFileName := fset.File(fileNode.Pos()).Name() + fileSuffix - newFileName := fset.File(fileNode.Pos()).Name() - e = os.Rename(oldFileName, newFileName) - if e != nil { - return nil, e - } - } - fileNodeSet = append(fileNodeSet, fileNode) - } - } - return fileNodeSet, nil -} diff --git a/instrgen/lib/callgraph.go b/instrgen/lib/callgraph.go deleted file mode 100644 index 03ac45f9c70..00000000000 --- a/instrgen/lib/callgraph.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib // import "go.opentelemetry.io/contrib/instrgen/lib" - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "strings" - - "golang.org/x/tools/go/packages" -) - -// FuncDescriptor stores an information about -// id, type and if function requires custom instrumentation. -type FuncDescriptor struct { - Id string - DeclType string - CustomInjection bool -} - -// Function TypeHash. Each function is itentified by its -// id and type. -func (fd FuncDescriptor) TypeHash() string { - return fd.Id + fd.DeclType -} - -// LoadMode. Tells about needed information during analysis. -const LoadMode packages.LoadMode = packages.NeedName | - packages.NeedTypes | - packages.NeedSyntax | - packages.NeedTypesInfo | - packages.NeedFiles - -func getPkgs(projectPath string, packagePattern string, fset *token.FileSet) ([]*packages.Package, error) { - cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: projectPath} - pkgs, err := packages.Load(cfg, packagePattern) - var packageSet []*packages.Package - if err != nil { - return nil, err - } - for _, pkg := range pkgs { - fmt.Println("\t", pkg) - packageSet = append(packageSet, pkg) - } - return packageSet, nil -} - -// FindRootFunctions looks for all root functions eg. entry points. -// Currently an entry point is a function that contains call of function -// passed as functionLabel paramaterer. -func FindRootFunctions(projectPath string, packagePattern string, functionLabel string) []FuncDescriptor { - fset := token.NewFileSet() - pkgs, _ := getPkgs(projectPath, packagePattern, fset) - var currentFun FuncDescriptor - var rootFunctions []FuncDescriptor - for _, pkg := range pkgs { - for _, node := range pkg.Syntax { - ast.Inspect(node, func(n ast.Node) bool { - switch xNode := n.(type) { - case *ast.CallExpr: - selector, ok := xNode.Fun.(*ast.SelectorExpr) - if ok { - if selector.Sel.Name == functionLabel { - rootFunctions = append(rootFunctions, currentFun) - } - } - case *ast.FuncDecl: - if pkg.TypesInfo.Defs[xNode.Name] != nil { - funId := pkg.TypesInfo.Defs[xNode.Name].Pkg().Path() + "." + pkg.TypesInfo.Defs[xNode.Name].Name() - currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} - fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) - } - } - return true - }) - } - } - return rootFunctions -} - -// GetMostInnerAstIdent takes most inner identifier used for -// function call. For a.b.foo(), `b` will be the most inner identifier. -func GetMostInnerAstIdent(inSel *ast.SelectorExpr) *ast.Ident { - var l []*ast.Ident - var e ast.Expr - e = inSel - for e != nil { - if _, ok := e.(*ast.Ident); ok { - l = append(l, e.(*ast.Ident)) - break - } else if _, ok := e.(*ast.SelectorExpr); ok { - l = append(l, e.(*ast.SelectorExpr).Sel) - e = e.(*ast.SelectorExpr).X - } else if _, ok := e.(*ast.CallExpr); ok { - e = e.(*ast.CallExpr).Fun - } else if _, ok := e.(*ast.IndexExpr); ok { - e = e.(*ast.IndexExpr).X - } else if _, ok := e.(*ast.UnaryExpr); ok { - e = e.(*ast.UnaryExpr).X - } else if _, ok := e.(*ast.ParenExpr); ok { - e = e.(*ast.ParenExpr).X - } else if _, ok := e.(*ast.SliceExpr); ok { - e = e.(*ast.SliceExpr).X - } else if _, ok := e.(*ast.IndexListExpr); ok { - e = e.(*ast.IndexListExpr).X - } else if _, ok := e.(*ast.StarExpr); ok { - e = e.(*ast.StarExpr).X - } else if _, ok := e.(*ast.TypeAssertExpr); ok { - e = e.(*ast.TypeAssertExpr).X - } else if _, ok := e.(*ast.CompositeLit); ok { - // TODO dummy implementation - if len(e.(*ast.CompositeLit).Elts) == 0 { - e = e.(*ast.CompositeLit).Type - } else { - e = e.(*ast.CompositeLit).Elts[0] - } - } else if _, ok := e.(*ast.KeyValueExpr); ok { - e = e.(*ast.KeyValueExpr).Value - } else { - // TODO this is uncaught expression - panic("uncaught expression") - } - } - if len(l) < 2 { - panic("selector list should have at least 2 elems") - } - // caller or receiver is always - // at position 1, function is at 0 - return l[1] -} - -// GetPkgPathFromRecvInterface builds package path taking -// receiver interface into account. -func GetPkgPathFromRecvInterface(pkg *packages.Package, - pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool, -) string { - var pkgPath string - for _, v := range funDeclNode.Recv.List { - for _, dependentpkg := range pkgs { - for _, defs := range dependentpkg.TypesInfo.Defs { - if defs == nil { - continue - } - if _, ok := defs.Type().Underlying().(*types.Interface); !ok { - continue - } - if len(v.Names) == 0 || pkg.TypesInfo.Defs[v.Names[0]] == nil { - continue - } - funType := pkg.TypesInfo.Defs[v.Names[0]].Type() - - if types.Implements(funType, defs.Type().Underlying().(*types.Interface)) { - interfaceExists := interfaces[defs.Type().String()] - if interfaceExists { - pkgPath = defs.Type().String() - } - break - } - } - } - } - return pkgPath -} - -// GetPkgPathFromFunctionRecv build package path taking function receiver parameters. -func GetPkgPathFromFunctionRecv(pkg *packages.Package, - pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool, -) string { - pkgPath := GetPkgPathFromRecvInterface(pkg, pkgs, funDeclNode, interfaces) - if len(pkgPath) != 0 { - return pkgPath - } - for _, v := range funDeclNode.Recv.List { - if len(v.Names) == 0 { - continue - } - funType := pkg.TypesInfo.Defs[v.Names[0]].Type() - pkgPath = funType.String() - // We don't care if that's pointer, remove it from - // type id - if _, ok := funType.(*types.Pointer); ok { - pkgPath = strings.TrimPrefix(pkgPath, "*") - } - // We don't care if called via index, remove it from - // type id - if _, ok := funType.(*types.Slice); ok { - pkgPath = strings.TrimPrefix(pkgPath, "[]") - } - } - - return pkgPath -} - -// GetSelectorPkgPath builds packages path according to selector expr. -func GetSelectorPkgPath(sel *ast.SelectorExpr, pkg *packages.Package, pkgPath string) string { - caller := GetMostInnerAstIdent(sel) - if caller != nil && pkg.TypesInfo.Uses[caller] != nil { - if !strings.Contains(pkg.TypesInfo.Uses[caller].Type().String(), "invalid") { - pkgPath = pkg.TypesInfo.Uses[caller].Type().String() - // We don't care if that's pointer, remove it from - // type id - if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Pointer); ok { - pkgPath = strings.TrimPrefix(pkgPath, "*") - } - // We don't care if called via index, remove it from - // type id - if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Slice); ok { - pkgPath = strings.TrimPrefix(pkgPath, "[]") - } - } - } - return pkgPath -} - -// GetPkgNameFromUsesTable gets package name from uses table. -func GetPkgNameFromUsesTable(pkg *packages.Package, ident *ast.Ident) string { - var pkgPath string - if pkg.TypesInfo.Uses[ident].Pkg() != nil { - pkgPath = pkg.TypesInfo.Uses[ident].Pkg().Path() - } - return pkgPath -} - -// GetPkgNameFromDefsTable gets package name from uses table. -func GetPkgNameFromDefsTable(pkg *packages.Package, ident *ast.Ident) string { - var pkgPath string - if pkg.TypesInfo.Defs[ident] == nil { - return pkgPath - } - if pkg.TypesInfo.Defs[ident].Pkg() != nil { - pkgPath = pkg.TypesInfo.Defs[ident].Pkg().Path() - } - return pkgPath -} - -// GetPkgPathForFunction builds package path, delegates work to -// other helper functions defined above. -func GetPkgPathForFunction(pkg *packages.Package, - pkgs []*packages.Package, funDecl *ast.FuncDecl, interfaces map[string]bool, -) string { - if funDecl.Recv != nil { - return GetPkgPathFromFunctionRecv(pkg, pkgs, funDecl, interfaces) - } - return GetPkgNameFromDefsTable(pkg, funDecl.Name) -} - -// BuildCallGraph builds an information about flow graph -// in the following form child->parent. -func BuildCallGraph( - projectPath string, - packagePattern string, - funcDecls map[FuncDescriptor]bool, - interfaces map[string]bool, -) map[FuncDescriptor][]FuncDescriptor { - fset := token.NewFileSet() - pkgs, _ := getPkgs(projectPath, packagePattern, fset) - fmt.Println("BuildCallGraph") - currentFun := FuncDescriptor{"nil", "", false} - backwardCallGraph := make(map[FuncDescriptor][]FuncDescriptor) - for _, pkg := range pkgs { - fmt.Println("\t", pkg) - for _, node := range pkg.Syntax { - fmt.Println("\t\t", fset.File(node.Pos()).Name()) - ast.Inspect(node, func(n ast.Node) bool { - switch xNode := n.(type) { - case *ast.CallExpr: - if id, ok := xNode.Fun.(*ast.Ident); ok { - pkgPath := GetPkgNameFromUsesTable(pkg, id) - funId := pkgPath + "." + pkg.TypesInfo.Uses[id].Name() - fmt.Println("\t\t\tFuncCall:", funId, pkg.TypesInfo.Uses[id].Type().String(), - " @called : ", - fset.File(node.Pos()).Name()) - fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[id].Type().String(), false} - if !Contains(backwardCallGraph[fun], currentFun) { - if funcDecls[fun] { - backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) - } - } - } - if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { - if pkg.TypesInfo.Uses[sel.Sel] != nil { - pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) - if sel.X != nil { - pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) - } - funId := pkgPath + "." + pkg.TypesInfo.Uses[sel.Sel].Name() - fmt.Println("\t\t\tFuncCall via selector:", funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), - " @called : ", - fset.File(node.Pos()).Name()) - fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), false} - if !Contains(backwardCallGraph[fun], currentFun) { - if funcDecls[fun] { - backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) - } - } - } - } - case *ast.FuncDecl: - if pkg.TypesInfo.Defs[xNode.Name] != nil { - pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, interfaces) - funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() - funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false}] = true - currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} - fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) - } - } - return true - }) - } - } - return backwardCallGraph -} - -// FindFuncDecls looks for all function declarations. -func FindFuncDecls(projectPath string, packagePattern string, interfaces map[string]bool) map[FuncDescriptor]bool { - fset := token.NewFileSet() - pkgs, _ := getPkgs(projectPath, packagePattern, fset) - fmt.Println("FindFuncDecls") - funcDecls := make(map[FuncDescriptor]bool) - for _, pkg := range pkgs { - fmt.Println("\t", pkg) - for _, node := range pkg.Syntax { - fmt.Println("\t\t", fset.File(node.Pos()).Name()) - ast.Inspect(node, func(n ast.Node) bool { - if funDeclNode, ok := n.(*ast.FuncDecl); ok { - pkgPath := GetPkgPathForFunction(pkg, pkgs, funDeclNode, interfaces) - if pkg.TypesInfo.Defs[funDeclNode.Name] != nil { - funId := pkgPath + "." + pkg.TypesInfo.Defs[funDeclNode.Name].Name() - fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String()) - funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String(), false}] = true - } - } - return true - }) - } - } - return funcDecls -} - -// FindInterfaces looks for all interfaces. -func FindInterfaces(projectPath string, packagePattern string) map[string]bool { - fset := token.NewFileSet() - pkgs, _ := getPkgs(projectPath, packagePattern, fset) - fmt.Println("FindInterfaces") - interaceTable := make(map[string]bool) - for _, pkg := range pkgs { - fmt.Println("\t", pkg) - for _, node := range pkg.Syntax { - fmt.Println("\t\t", fset.File(node.Pos()).Name()) - ast.Inspect(node, func(n ast.Node) bool { - if typeSpecNode, ok := n.(*ast.TypeSpec); ok { - if _, ok := typeSpecNode.Type.(*ast.InterfaceType); ok { - fmt.Println("\t\t\tInterface:", pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()) - interaceTable[pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()] = true - } - } - return true - }) - } - } - return interaceTable -} - -// InferRootFunctionsFromGraph tries to infer entry points from passed call graph. -func InferRootFunctionsFromGraph(callgraph map[FuncDescriptor][]FuncDescriptor) []FuncDescriptor { - var allFunctions map[FuncDescriptor]bool - var rootFunctions []FuncDescriptor - allFunctions = make(map[FuncDescriptor]bool) - for k, v := range callgraph { - allFunctions[k] = true - for _, childFun := range v { - allFunctions[childFun] = true - } - } - for k := range allFunctions { - _, exists := callgraph[k] - if !exists { - rootFunctions = append(rootFunctions, k) - } - } - return rootFunctions -} diff --git a/instrgen/lib/context_propagation.go b/instrgen/lib/context_propagation.go deleted file mode 100644 index 0a720538872..00000000000 --- a/instrgen/lib/context_propagation.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib // import "go.opentelemetry.io/contrib/instrgen/lib" - -import ( - "fmt" - "go/ast" - - "golang.org/x/tools/go/packages" -) - -func isFunPartOfCallGraph(fun FuncDescriptor, callgraph map[FuncDescriptor][]FuncDescriptor) bool { - // TODO this is not optimap o(n) - for k, v := range callgraph { - if k.TypeHash() == fun.TypeHash() { - return true - } - for _, e := range v { - if fun.TypeHash() == e.TypeHash() { - return true - } - } - } - return false -} - -// ContextPropagationPass. -type ContextPropagationPass struct{} - -// Execute. -func (pass *ContextPropagationPass) Execute( - node *ast.File, - analysis *PackageAnalysis, - pkg *packages.Package, - pkgs []*packages.Package, -) []Import { - var imports []Import - addImports := false - // below variable is used - // when callexpr is inside var decl - // instead of functiondecl - currentFun := FuncDescriptor{} - emitEmptyContext := func(callExpr *ast.CallExpr, fun FuncDescriptor, ctxArg *ast.Ident) { - addImports = true - if currentFun != (FuncDescriptor{}) { - visited := map[FuncDescriptor]bool{} - if isPath(analysis.Callgraph, currentFun, analysis.RootFunctions[0], visited) { - callExpr.Args = append([]ast.Expr{ctxArg}, callExpr.Args...) - } else { - contextTodo := &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_context", - }, - Sel: &ast.Ident{ - Name: "TODO", - }, - }, - Lparen: 62, - Ellipsis: 0, - } - callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) - } - return - } - contextTodo := &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_context", - }, - Sel: &ast.Ident{ - Name: "TODO", - }, - }, - Lparen: 62, - Ellipsis: 0, - } - callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) - } - emitCallExpr := func(ident *ast.Ident, n ast.Node, ctxArg *ast.Ident, pkgPath string) { - if callExpr, ok := n.(*ast.CallExpr); ok { - funId := pkgPath + "." + pkg.TypesInfo.Uses[ident].Name() - fun := FuncDescriptor{ - Id: funId, - DeclType: pkg.TypesInfo.Uses[ident].Type().String(), - CustomInjection: false, - } - found := analysis.FuncDecls[fun] - - // inject context parameter only - // to these functions for which function decl - // exists - - if found { - visited := map[FuncDescriptor]bool{} - if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { - fmt.Println("\t\t\tContextPropagation FuncCall:", funId, pkg.TypesInfo.Uses[ident].Type().String()) - emitEmptyContext(callExpr, fun, ctxArg) - } - } - } - } - ast.Inspect(node, func(n ast.Node) bool { - ctxArg := &ast.Ident{ - Name: "__atel_child_tracing_ctx", - } - ctxField := &ast.Field{ - Names: []*ast.Ident{ - { - Name: "__atel_tracing_ctx", - }, - }, - Type: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_context", - }, - Sel: &ast.Ident{ - Name: "Context", - }, - }, - } - switch xNode := n.(type) { - case *ast.FuncDecl: - pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, analysis.Interfaces) - funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() - fun := FuncDescriptor{ - Id: funId, - DeclType: pkg.TypesInfo.Defs[xNode.Name].Type().String(), - CustomInjection: false, - } - currentFun = fun - // inject context only - // functions available in the call graph - if !isFunPartOfCallGraph(fun, analysis.Callgraph) { - break - } - - if Contains(analysis.RootFunctions, fun) { - break - } - visited := map[FuncDescriptor]bool{} - - if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { - fmt.Println("\t\t\tContextPropagation FuncDecl:", funId, - pkg.TypesInfo.Defs[xNode.Name].Type().String()) - addImports = true - xNode.Type.Params.List = append([]*ast.Field{ctxField}, xNode.Type.Params.List...) - } - case *ast.CallExpr: - if ident, ok := xNode.Fun.(*ast.Ident); ok { - if pkg.TypesInfo.Uses[ident] == nil { - return false - } - pkgPath := GetPkgNameFromUsesTable(pkg, ident) - emitCallExpr(ident, n, ctxArg, pkgPath) - } - - if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { - if pkg.TypesInfo.Uses[sel.Sel] == nil { - return false - } - pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) - if sel.X != nil { - pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) - } - emitCallExpr(sel.Sel, n, ctxArg, pkgPath) - } - - case *ast.TypeSpec: - iname := xNode.Name - iface, ok := xNode.Type.(*ast.InterfaceType) - if !ok { - return true - } - for _, method := range iface.Methods.List { - funcType, ok := method.Type.(*ast.FuncType) - if !ok { - return true - } - visited := map[FuncDescriptor]bool{} - pkgPath := GetPkgNameFromDefsTable(pkg, method.Names[0]) - funId := pkgPath + "." + iname.Name + "." + pkg.TypesInfo.Defs[method.Names[0]].Name() - fun := FuncDescriptor{ - Id: funId, - DeclType: pkg.TypesInfo.Defs[method.Names[0]].Type().String(), - CustomInjection: false, - } - if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { - fmt.Println("\t\t\tContext Propagation InterfaceType", fun.Id, fun.DeclType) - addImports = true - funcType.Params.List = append([]*ast.Field{ctxField}, funcType.Params.List...) - } - } - } - return true - }) - if addImports { - imports = append(imports, Import{"__atel_context", "context", Add}) - } - return imports -} diff --git a/instrgen/lib/instrumentation.go b/instrgen/lib/instrumentation.go deleted file mode 100644 index 4be2fd94795..00000000000 --- a/instrgen/lib/instrumentation.go +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib // import "go.opentelemetry.io/contrib/instrgen/lib" - -import ( - "fmt" - "go/ast" - "go/token" - - "golang.org/x/tools/go/packages" -) - -// InstrumentationPass. -type InstrumentationPass struct{} - -func makeInitStmts(name string) []ast.Stmt { - childTracingSupress := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "_", - }, - }, - Tok: token.ASSIGN, - Rhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_child_tracing_ctx", - }, - }, - } - s1 := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_ts", - }, - }, - Tok: token.DEFINE, - - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "rtlib", - }, - Sel: &ast.Ident{ - Name: "NewTracingState", - }, - }, - Lparen: 54, - Ellipsis: 0, - }, - }, - } - s2 := &ast.DeferStmt{ - Defer: 27, - Call: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "rtlib", - }, - Sel: &ast.Ident{ - Name: "Shutdown", - }, - }, - Lparen: 48, - Args: []ast.Expr{ - &ast.Ident{ - Name: "__atel_ts", - }, - }, - Ellipsis: 0, - }, - } - - s3 := &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_otel", - }, - Sel: &ast.Ident{ - Name: "SetTracerProvider", - }, - }, - Lparen: 49, - Args: []ast.Expr{ - &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_ts", - }, - Sel: &ast.Ident{ - Name: "Tp", - }, - }, - }, - Ellipsis: 0, - }, - } - s4 := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_ctx", - }, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_context", - }, - Sel: &ast.Ident{ - Name: "Background", - }, - }, - Lparen: 52, - Ellipsis: 0, - }, - }, - } - s5 := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_child_tracing_ctx", - }, - &ast.Ident{ - Name: "__atel_span", - }, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_otel", - }, - Sel: &ast.Ident{ - Name: "Tracer", - }, - }, - Lparen: 50, - Args: []ast.Expr{ - &ast.Ident{ - Name: `"` + name + `"`, - }, - }, - Ellipsis: 0, - }, - Sel: &ast.Ident{ - Name: "Start", - }, - }, - Lparen: 62, - Args: []ast.Expr{ - &ast.Ident{ - Name: "__atel_ctx", - }, - &ast.Ident{ - Name: `"` + name + `"`, - }, - }, - Ellipsis: 0, - }, - }, - } - - s6 := &ast.DeferStmt{ - Defer: 27, - Call: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_span", - }, - Sel: &ast.Ident{ - Name: "End", - }, - }, - Lparen: 41, - Ellipsis: 0, - }, - } - stmts := []ast.Stmt{s1, s2, s3, s4, s5, childTracingSupress, s6} - return stmts -} - -func makeSpanStmts(name string, paramName string) []ast.Stmt { - s1 := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_child_tracing_ctx", - }, - &ast.Ident{ - Name: "__atel_span", - }, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_otel", - }, - Sel: &ast.Ident{ - Name: "Tracer", - }, - }, - Lparen: 50, - Args: []ast.Expr{ - &ast.Ident{ - Name: `"` + name + `"`, - }, - }, - Ellipsis: 0, - }, - Sel: &ast.Ident{ - Name: "Start", - }, - }, - Lparen: 62, - Args: []ast.Expr{ - &ast.Ident{ - Name: paramName, - }, - &ast.Ident{ - Name: `"` + name + `"`, - }, - }, - Ellipsis: 0, - }, - }, - } - - s2 := &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{ - Name: "_", - }, - }, - Tok: token.ASSIGN, - Rhs: []ast.Expr{ - &ast.Ident{ - Name: "__atel_child_tracing_ctx", - }, - }, - } - - s3 := &ast.DeferStmt{ - Defer: 27, - Call: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{ - Name: "__atel_span", - }, - Sel: &ast.Ident{ - Name: "End", - }, - }, - Lparen: 41, - Ellipsis: 0, - }, - } - stmts := []ast.Stmt{s1, s2, s3} - return stmts -} - -// Execute. -func (pass *InstrumentationPass) Execute( - node *ast.File, - analysis *PackageAnalysis, - pkg *packages.Package, - pkgs []*packages.Package, -) []Import { - var imports []Import - addImports := false - addContext := false - // store all function literals positions - // that are part of assignment statement - // it's used to avoid injection into literal - // more than once - var functionLiteralPositions []token.Pos - ast.Inspect(node, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.FuncDecl: - pkgPath := GetPkgPathForFunction(pkg, pkgs, x, analysis.Interfaces) - fundId := pkgPath + "." + pkg.TypesInfo.Defs[x.Name].Name() - fun := FuncDescriptor{ - Id: fundId, - DeclType: pkg.TypesInfo.Defs[x.Name].Type().String(), - CustomInjection: false, - } - // check if it's root function or - // one of function in call graph - // and emit proper ast nodes - _, exists := analysis.Callgraph[fun] - if !exists { - if !Contains(analysis.RootFunctions, fun) { - return false - } - } - for _, root := range analysis.RootFunctions { - visited := map[FuncDescriptor]bool{} - fmt.Println("\t\t\tInstrumentation FuncDecl:", fundId, pkg.TypesInfo.Defs[x.Name].Type().String()) - if isPath(analysis.Callgraph, fun, root, visited) && fun.TypeHash() != root.TypeHash() { - x.Body.List = append(makeSpanStmts(x.Name.Name, "__atel_tracing_ctx"), x.Body.List...) - addContext = true - addImports = true - } else { - // check whether this function is root function - if !Contains(analysis.RootFunctions, fun) { - return false - } - x.Body.List = append(makeInitStmts(x.Name.Name), x.Body.List...) - addContext = true - addImports = true - } - } - case *ast.AssignStmt: - for _, e := range x.Lhs { - if ident, ok := e.(*ast.Ident); ok { - _ = ident - pkgPath := "" - pkgPath = GetPkgNameFromDefsTable(pkg, ident) - if pkg.TypesInfo.Defs[ident] == nil { - return false - } - fundId := pkgPath + "." + pkg.TypesInfo.Defs[ident].Name() - fun := FuncDescriptor{ - Id: fundId, - DeclType: pkg.TypesInfo.Defs[ident].Type().String(), - CustomInjection: true, - } - _, exists := analysis.Callgraph[fun] - if exists { - return false - } - } - } - for _, e := range x.Rhs { - if funLit, ok := e.(*ast.FuncLit); ok { - functionLiteralPositions = append(functionLiteralPositions, funLit.Pos()) - funLit.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), funLit.Body.List...) - addImports = true - addContext = true - } - } - case *ast.FuncLit: - for _, pos := range functionLiteralPositions { - if pos == x.Pos() { - return false - } - } - x.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), x.Body.List...) - addImports = true - addContext = true - } - - return true - }) - if addContext { - imports = append(imports, Import{"__atel_context", "context", Add}) - } - if addImports { - imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Add}) - } - return imports -} diff --git a/instrgen/lib/rewriter.go b/instrgen/lib/rewriter.go new file mode 100644 index 00000000000..efc9fa8cab2 --- /dev/null +++ b/instrgen/lib/rewriter.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "go/ast" + "go/token" + "os" +) + +// PackageRewriter interface does actual input package +// rewriting according to specific criteria. +type PackageRewriter interface { + // ID Dumps rewriter id. + Id() string + // Inject tells whether package should be rewritten. + Inject(pkg string, filepath string) bool + // ReplaceSource decides whether input sources should be replaced + // or all rewriting work should be done in temporary location. + ReplaceSource(pkg string, filePath string) bool + // Rewrite does actual package rewriting. + Rewrite(pkg string, file *ast.File, fset *token.FileSet, trace *os.File) + // WriteExtraFiles generate additional files that will be linked + // together to input package. + // Additional files have to be returned as array of file names. + WriteExtraFiles(pkg string, destPath string) []string +} diff --git a/instrgen/lib/tools.go b/instrgen/lib/tools.go index e366a6f8de5..2fac97a7201 100644 --- a/instrgen/lib/tools.go +++ b/instrgen/lib/tools.go @@ -34,38 +34,22 @@ func SearchFiles(root string, ext string) []string { return files } -func isPath( - callGraph map[FuncDescriptor][]FuncDescriptor, - current FuncDescriptor, - goal FuncDescriptor, - visited map[FuncDescriptor]bool, -) bool { - if current == goal { - return true - } +// CreateFile. +func CreateFile(name string) (*os.File, error) { + var out *os.File + out, err := os.Create(name) - value, ok := callGraph[current] - if ok { - for _, child := range value { - exists := visited[child] - if exists { - continue - } - visited[child] = true - if isPath(callGraph, child, goal, visited) { - return true - } - } + if err != nil { + return nil, err } - return false + return out, err } -// Contains. -func Contains(a []FuncDescriptor, x FuncDescriptor) bool { - for _, n := range a { - if x.TypeHash() == n.TypeHash() { - return true - } +// FileExists. +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false } - return false + return !info.IsDir() } diff --git a/instrgen/rewriters/basic_rewriter.go b/instrgen/rewriters/basic_rewriter.go new file mode 100644 index 00000000000..283a9157854 --- /dev/null +++ b/instrgen/rewriters/basic_rewriter.go @@ -0,0 +1,758 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rewriters // import "go.opentelemetry.io/contrib/instrgen/rewriters" + +import ( + "go/ast" + "go/token" + "golang.org/x/tools/go/ast/astutil" + "os" + "strings" +) + +func makeInitStmts(name string) []ast.Stmt { + childTracingSupress := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + } + s1 := + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ts", + }, + }, + Tok: token.DEFINE, + + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "rtlib", + }, + Sel: &ast.Ident{ + Name: "NewTracingState", + }, + }, + Lparen: 54, + Ellipsis: 0, + }, + }, + } + s2 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "rtlib", + }, + Sel: &ast.Ident{ + Name: "Shutdown", + }, + }, + Lparen: 48, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ts", + }, + }, + Ellipsis: 0, + }, + } + + s3 := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "SetTracerProvider", + }, + }, + Lparen: 49, + Args: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_ts", + }, + Sel: &ast.Ident{ + Name: "Tp", + }, + }, + }, + Ellipsis: 0, + }, + } + s4 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ctx", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "Background", + }, + }, + Lparen: 52, + Ellipsis: 0, + }, + }, + } + s5 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + &ast.Ident{ + Name: "__atel_span", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "Tracer", + }, + }, + Lparen: 50, + Args: []ast.Expr{ + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "Start", + }, + }, + Lparen: 62, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ctx", + }, + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + }, + } + + s6 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Sel: &ast.Ident{ + Name: "End", + }, + }, + Lparen: 41, + Ellipsis: 0, + }, + } + + s7 := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_runtime", + }, + Sel: &ast.Ident{ + Name: "InstrgenSetTls", + }, + }, + Lparen: 56, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + Ellipsis: 0, + }, + } + s8 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_spanCtx", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_trace", + }, + Sel: &ast.Ident{ + Name: "SpanContextFromContext", + }, + }, + Lparen: 68, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + Ellipsis: 0, + }, + }, + } + s9 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_spanCtx", + }, + }, + } + + s10 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.BasicLit{ + ValuePos: 45, + Kind: token.STRING, + Value: "\"\"", + }, + }, + } + s11 := &ast.IfStmt{ + If: 35, + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_rdspan", + }, + &ast.Ident{ + Name: "ok", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.TypeAssertExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Lparen: 71, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_sdktrace", + }, + Sel: &ast.Ident{ + Name: "ReadOnlySpan", + }, + }, + }, + }, + }, + Cond: &ast.Ident{ + Name: "ok", + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_rdspan", + }, + Sel: &ast.Ident{ + Name: "Parent", + }, + }, + Lparen: 167, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "SpanID", + }, + }, + Lparen: 176, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "String", + }, + }, + Lparen: 185, + Ellipsis: 0, + }, + }, + }, + }, + }, + } + s12 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + } + _ = s11 + _ = s10 + stmts := []ast.Stmt{s1, s2, s3, s4, s5, childTracingSupress, s6, s7, s8, s9, s10, s11, s12} + return stmts +} + +func makeSpanStmts(name string, paramName string) []ast.Stmt { + s0 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_tracing_ctx", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "Background", + }, + }, + Lparen: 67, + Ellipsis: 0, + }, + }, + } + s1 := &ast.IfStmt{ + If: 35, + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_tracing_ctx_runtime", + }, + &ast.Ident{ + Name: "ok", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.TypeAssertExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_runtime", + }, + Sel: &ast.Ident{ + Name: "InstrgenGetTls", + }, + }, + Lparen: 93, + Ellipsis: 0, + }, + Lparen: 96, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "Context", + }, + }, + }, + }, + }, + Cond: &ast.Ident{ + Name: "ok", + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_tracing_ctx", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_tracing_ctx_runtime", + }, + }, + }, + }, + }, + } + + s2 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_runtime", + }, + Sel: &ast.Ident{ + Name: "InstrgenSetTls", + }, + }, + Lparen: 62, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_tracing_ctx", + }, + }, + Ellipsis: 0, + }, + } + + s3 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + &ast.Ident{ + Name: "__atel_span", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "Tracer", + }, + }, + Lparen: 50, + Args: []ast.Expr{ + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "Start", + }, + }, + Lparen: 62, + Args: []ast.Expr{ + &ast.Ident{ + Name: paramName, + }, + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + }, + } + s4 := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_runtime", + }, + Sel: &ast.Ident{ + Name: "InstrgenSetTls", + }, + }, + Lparen: 56, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + Ellipsis: 0, + }, + } + + s5 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Sel: &ast.Ident{ + Name: "End", + }, + }, + Lparen: 41, + Ellipsis: 0, + }, + } + + s6 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_spanCtx", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_trace", + }, + Sel: &ast.Ident{ + Name: "SpanContextFromContext", + }, + }, + Lparen: 68, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + Ellipsis: 0, + }, + }, + } + s7 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_spanCtx", + }, + }, + } + + s8 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.BasicLit{ + ValuePos: 45, + Kind: token.STRING, + Value: "\"\"", + }, + }, + } + + s9 := &ast.IfStmt{ + If: 35, + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_rdspan", + }, + &ast.Ident{ + Name: "ok", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.TypeAssertExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Lparen: 71, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_sdktrace", + }, + Sel: &ast.Ident{ + Name: "ReadOnlySpan", + }, + }, + }, + }, + }, + Cond: &ast.Ident{ + Name: "ok", + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_rdspan", + }, + Sel: &ast.Ident{ + Name: "Parent", + }, + }, + Lparen: 167, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "SpanID", + }, + }, + Lparen: 176, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "String", + }, + }, + Lparen: 185, + Ellipsis: 0, + }, + }, + }, + }, + }, + } + s10 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_parent_span_id", + }, + }, + } + _ = s9 + _ = s8 + stmts := []ast.Stmt{s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10} + + return stmts +} + +// BasicRewriter rewrites all functions according to FilePattern. +type BasicRewriter struct { + FilePattern string + Replace string + Pkg string + Fun string + RemappedFilePaths map[string]string +} + +// Id. +func (BasicRewriter) Id() string { + return "Basic" +} + +// Inject. +func (b BasicRewriter) Inject(pkg string, filepath string) bool { + return strings.Contains(filepath, b.FilePattern) || strings.Contains(b.RemappedFilePaths[filepath], b.FilePattern) +} + +// ReplaceSource. +func (b BasicRewriter) ReplaceSource(pkg string, filePath string) bool { + return b.Replace == "yes" +} + +// Rewrite. +func (b BasicRewriter) Rewrite(pkg string, file *ast.File, fset *token.FileSet, trace *os.File) { + visited := make(map[string]bool, 0) + ast.Inspect(file, func(n ast.Node) bool { + if funDeclNode, ok := n.(*ast.FuncDecl); ok { + // check if functions has been already instrumented + if _, ok := visited[fset.Position(file.Pos()).String()+":"+funDeclNode.Name.Name]; !ok { + if pkg == b.Pkg && funDeclNode.Name.Name == b.Fun { + astutil.AddImport(fset, file, "go.opentelemetry.io/contrib/instrgen/rtlib") + funDeclNode.Body.List = append(makeInitStmts(funDeclNode.Name.Name), funDeclNode.Body.List...) + } else { + funDeclNode.Body.List = append(makeSpanStmts(funDeclNode.Name.Name, "__atel_tracing_ctx"), funDeclNode.Body.List...) + } + astutil.AddNamedImport(fset, file, "__atel_trace", "go.opentelemetry.io/otel/trace") + astutil.AddNamedImport(fset, file, "__atel_sdktrace", "go.opentelemetry.io/otel/sdk/trace") + astutil.AddNamedImport(fset, file, "__atel_context", "context") + astutil.AddNamedImport(fset, file, "__atel_otel", "go.opentelemetry.io/otel") + astutil.AddNamedImport(fset, file, "__atel_runtime", "runtime") + visited[fset.Position(file.Pos()).String()+":"+funDeclNode.Name.Name] = true + } + } + return true + }) +} + +// WriteExtraFiles. +func (BasicRewriter) WriteExtraFiles(pkg string, destPath string) []string { + return nil +} diff --git a/instrgen/lib/otel_pruning.go b/instrgen/rewriters/otel_pruner.go similarity index 53% rename from instrgen/lib/otel_pruning.go rename to instrgen/rewriters/otel_pruner.go index a098d8df636..0dc0adce062 100644 --- a/instrgen/lib/otel_pruning.go +++ b/instrgen/rewriters/otel_pruner.go @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package lib // import "go.opentelemetry.io/contrib/instrgen/lib" +package rewriters // import "go.opentelemetry.io/contrib/instrgen/rewriters" import ( "go/ast" + "go/token" + "os" "strings" - "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/ast/astutil" ) func removeStmt(slice []ast.Stmt, s int) []ast.Stmt { @@ -33,41 +35,70 @@ func removeExpr(slice []ast.Expr, s int) []ast.Expr { return append(slice[:s], slice[s+1:]...) } -// OtelPruner. -type OtelPruner struct{} - -func inspectFuncContent(fType *ast.FuncType, fBody *ast.BlockStmt) { +func inspectFuncContent(fType *ast.FuncType, fBody *ast.BlockStmt, remove bool) bool { + instrgenCode := false for index := 0; index < len(fType.Params.List); index++ { param := fType.Params.List[index] for _, ident := range param.Names { if strings.Contains(ident.Name, "__atel_") { - fType.Params.List = removeField(fType.Params.List, index) - index-- + if remove == true { + fType.Params.List = removeField(fType.Params.List, index) + index-- + } + instrgenCode = true } } } for index := 0; index < len(fBody.List); index++ { stmt := fBody.List[index] switch bodyStmt := stmt.(type) { + case *ast.IfStmt: + if assigment, ok := bodyStmt.Init.(*ast.AssignStmt); ok { + if ident, ok := assigment.Lhs[0].(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true + } + } + } case *ast.AssignStmt: if ident, ok := bodyStmt.Lhs[0].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { - fBody.List = removeStmt(fBody.List, index) - index-- + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true } } if ident, ok := bodyStmt.Rhs[0].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { - fBody.List = removeStmt(fBody.List, index) - index-- + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true } } case *ast.ExprStmt: if call, ok := bodyStmt.X.(*ast.CallExpr); ok { if sel, ok := call.Fun.(*ast.SelectorExpr); ok { if strings.Contains(sel.Sel.Name, "SetTracerProvider") { - fBody.List = removeStmt(fBody.List, index) - index-- + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true + } + if strings.Contains(sel.Sel.Name, "InstrgenSetTls") { + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true } } } @@ -76,40 +107,46 @@ func inspectFuncContent(fType *ast.FuncType, fBody *ast.BlockStmt) { if strings.Contains(sel.Sel.Name, "Shutdown") { if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "rtlib") { - fBody.List = removeStmt(fBody.List, index) - index-- + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true } } } if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { - fBody.List = removeStmt(fBody.List, index) - index-- + if remove == true { + fBody.List = removeStmt(fBody.List, index) + index-- + } + instrgenCode = true } } } } } + return instrgenCode } -// Execute. -func (pass *OtelPruner) Execute( - node *ast.File, - analysis *PackageAnalysis, - pkg *packages.Package, - pkgs []*packages.Package, -) []Import { - var imports []Import - ast.Inspect(node, func(n ast.Node) bool { +func inspect(file *ast.File, remove bool) bool { + instrgenCode := false + ast.Inspect(file, func(n ast.Node) bool { switch x := n.(type) { case *ast.FuncDecl: - inspectFuncContent(x.Type, x.Body) + if x.Body != nil { + instrgenCode = inspectFuncContent(x.Type, x.Body, remove) + } case *ast.CallExpr: for argIndex := 0; argIndex < len(x.Args); argIndex++ { if ident, ok := x.Args[argIndex].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { - x.Args = removeExpr(x.Args, argIndex) - argIndex-- + if remove == true { + x.Args = removeExpr(x.Args, argIndex) + argIndex-- + } + instrgenCode = true } } } @@ -118,15 +155,18 @@ func (pass *OtelPruner) Execute( if sel, ok := c.Fun.(*ast.SelectorExpr); ok { if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { - x.Args = removeExpr(x.Args, argIndex) - argIndex-- + if remove == true { + x.Args = removeExpr(x.Args, argIndex) + argIndex-- + } + instrgenCode = true } } } } } case *ast.FuncLit: - inspectFuncContent(x.Type, x.Body) + instrgenCode = inspectFuncContent(x.Type, x.Body, remove) case *ast.TypeSpec: iface, ok := x.Type.(*ast.InterfaceType) if !ok { @@ -140,8 +180,11 @@ func (pass *OtelPruner) Execute( for argIndex := 0; argIndex < len(funcType.Params.List); argIndex++ { for _, ident := range funcType.Params.List[argIndex].Names { if strings.Contains(ident.Name, "__atel_") { - funcType.Params.List = removeField(funcType.Params.List, argIndex) - argIndex-- + if remove == true { + funcType.Params.List = removeField(funcType.Params.List, argIndex) + argIndex-- + } + instrgenCode = true } } } @@ -149,9 +192,42 @@ func (pass *OtelPruner) Execute( } return true }) - imports = append(imports, Import{"__atel_context", "context", Remove}) - imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Remove}) - imports = append(imports, Import{"__atel_otelhttp", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Remove}) + return instrgenCode +} + +// OtelPruner. +type OtelPruner struct { + FilePattern string + Replace bool +} + +// Id. +func (OtelPruner) Id() string { + return "Pruner" +} + +// Inject. +func (pruner OtelPruner) Inject(pkg string, filepath string) bool { + return strings.Contains(filepath, pruner.FilePattern) +} + +// ReplaceSource. +func (pruner OtelPruner) ReplaceSource(pkg string, filePath string) bool { + return pruner.Replace +} + +// Rewrite. +func (OtelPruner) Rewrite(pkg string, file *ast.File, fset *token.FileSet, trace *os.File) { + inspect(file, true) + astutil.DeleteNamedImport(fset, file, "__atel_context", "context") + astutil.DeleteNamedImport(fset, file, "__atel_otel", "go.opentelemetry.io/otel") + astutil.DeleteNamedImport(fset, file, "__atel_runtime", "runtime") + astutil.DeleteNamedImport(fset, file, "__atel_trace", "go.opentelemetry.io/otel/trace") + astutil.DeleteNamedImport(fset, file, "__atel_sdktrace", "go.opentelemetry.io/otel/sdk/trace") + astutil.DeleteImport(fset, file, "go.opentelemetry.io/contrib/instrgen/rtlib") +} - return imports +// WriteExtraFiles. +func (OtelPruner) WriteExtraFiles(pkg string, destPath string) []string { + return nil } diff --git a/instrgen/rewriters/runtime_rewriter.go b/instrgen/rewriters/runtime_rewriter.go new file mode 100644 index 00000000000..e43e8809cb9 --- /dev/null +++ b/instrgen/rewriters/runtime_rewriter.go @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rewriters // import "go.opentelemetry.io/contrib/instrgen/rewriters" + +import ( + "go/ast" + "go/token" + "os" + + "go.opentelemetry.io/contrib/instrgen/lib" +) + +// RuntimeRewriter. +type RuntimeRewriter struct { + FilePattern string +} + +// Id. +func (RuntimeRewriter) Id() string { + return "runtime" +} + +// Inject. +func (RuntimeRewriter) Inject(pkg string, filepath string) bool { + return pkg == "runtime" +} + +// ReplaceSource. +func (RuntimeRewriter) ReplaceSource(pkg string, filePath string) bool { + return false +} + +// Rewrite. +func (RuntimeRewriter) Rewrite(pkg string, file *ast.File, fset *token.FileSet, trace *os.File) { + ast.Inspect(file, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.TypeSpec: + if n.Name != nil && n.Name.Name != "g" { + return false + } + st, ok := n.Type.(*ast.StructType) + if !ok { + return false + } + + s1 := &ast.Field{ + Names: []*ast.Ident{ + { + Name: "_tls_instrgen", + }, + }, + Type: &ast.Ident{ + Name: "interface{}", + }, + } + st.Fields.List = append(st.Fields.List, s1) + case *ast.FuncDecl: + if n.Name.Name != "newproc1" { + return false + } + if len(n.Type.Results.List) != 1 { + return false + } + if len(n.Type.Params.List) != 3 { + return false + } + deferStmt := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.FuncLit{ + Type: &ast.FuncType{ + Func: 33, + Params: &ast.FieldList{}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "instrgen_result", + }, + Sel: &ast.Ident{ + Name: "_tls_instrgen", + }, + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "callergp", + }, + Sel: &ast.Ident{ + Name: "_tls_instrgen", + }, + }, + }, + }, + }, + }, + }, + Lparen: 94, + Ellipsis: 0, + }, + } + + n.Body.List = append([]ast.Stmt{deferStmt}, n.Body.List...) + n.Type.Results.List[0].Names = append(n.Type.Results.List[0].Names, &ast.Ident{ + Name: "instrgen_result", + }) + } + + return true + }) +} + +// WriteExtraFiles. +func (RuntimeRewriter) WriteExtraFiles(pkg string, destPath string) []string { + ctxPropagation := `package runtime + +import ( + _ "unsafe" +) + +//go:nosplit +func InstrgenGetTls() interface{} { + return getg().m.curg._tls_instrgen +} + +//go:nosplit +func InstrgenSetTls(tls interface{}) { + getg().m.curg._tls_instrgen = tls +} +` + destination := destPath + "/" + "instrgen_tls.go" + if lib.FileExists(destination) { + return nil + } + tlsFile, err := os.Create(destination) + if err != nil { + return nil + } + _, err = tlsFile.WriteString(ctxPropagation) + if err != nil { + return nil + } + return []string{destination} +}