From 4aa4632c5aea314731712d2d24ebdac6c7034eba Mon Sep 17 00:00:00 2001 From: Przemek Delewski Date: Wed, 5 Jul 2023 10:31:56 +0200 Subject: [PATCH] utilize toolexec --- .github/dependabot.yml | 9 - instrgen/README.md | 53 +- instrgen/docs/flow.png | Bin 81837 -> 0 bytes instrgen/docs/how-it-works.md | 11 +- instrgen/driver/go.mod | 3 +- instrgen/driver/go.sum | 2 - instrgen/driver/instrgen_test.go | 193 +++-- instrgen/driver/main.go | 487 ++++++++--- instrgen/driver/passes.go | 52 -- instrgen/driver/testdata/basic/fib.go | 2 + instrgen/driver/testdata/basic/goroutines.go | 2 + instrgen/driver/testdata/basic/main.go | 3 +- instrgen/driver/testdata/basic/methods.go | 7 + instrgen/driver/testdata/basic/package.go | 3 + .../driver/testdata/expected/basic/fib.go | 32 +- .../testdata/expected/basic/goroutines.go | 14 +- .../driver/testdata/expected/basic/main.go | 25 +- .../driver/testdata/expected/basic/methods.go | 45 +- .../driver/testdata/expected/basic/package.go | 16 +- .../testdata/expected/interface/app/impl.go | 7 +- .../testdata/expected/interface/main.go | 4 +- .../interface/serializer/interface.go | 4 +- .../driver/testdata/expected/selector/main.go | 2 + instrgen/driver/testdata/interface/go.mod | 18 - instrgen/driver/testdata/interface/go.sum | 22 - instrgen/go.mod | 1 - instrgen/go.sum | 2 - instrgen/lib/analysis.go | 141 ---- instrgen/lib/callgraph.go | 396 --------- instrgen/lib/context_propagation.go | 213 ----- instrgen/lib/instrumentation.go | 381 --------- instrgen/lib/rewriter.go | 39 + instrgen/lib/tools.go | 42 +- instrgen/rewriters/basic_rewriter.go | 758 ++++++++++++++++++ .../otel_pruner.go} | 154 +++- instrgen/rewriters/runtime_rewriter.go | 161 ++++ 36 files changed, 1779 insertions(+), 1525 deletions(-) delete mode 100644 instrgen/docs/flow.png delete mode 100644 instrgen/driver/passes.go delete mode 100644 instrgen/driver/testdata/interface/go.mod delete mode 100644 instrgen/driver/testdata/interface/go.sum delete mode 100644 instrgen/lib/analysis.go delete mode 100644 instrgen/lib/callgraph.go delete mode 100644 instrgen/lib/context_propagation.go delete mode 100644 instrgen/lib/instrumentation.go create mode 100644 instrgen/lib/rewriter.go create mode 100644 instrgen/rewriters/basic_rewriter.go rename instrgen/{lib/otel_pruning.go => rewriters/otel_pruner.go} (53%) create mode 100644 instrgen/rewriters/runtime_rewriter.go 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 3f519e41f70992bec9c90c36f27beb350f1d2f35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81837 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�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 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} +}