| // Licensed to Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright |
| // ownership. Apache Software Foundation (ASF) licenses this file to you 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 rewrite |
| |
| import ( |
| "fmt" |
| "go/parser" |
| "path/filepath" |
| "strconv" |
| "strings" |
| |
| "github.com/dave/dst" |
| "github.com/dave/dst/decorator" |
| "github.com/dave/dst/dstutil" |
| |
| "golang.org/x/text/cases" |
| "golang.org/x/text/language" |
| |
| "github.com/apache/skywalking-go/tools/go-agent/instrument/consts" |
| "github.com/apache/skywalking-go/tools/go-agent/tools" |
| ) |
| |
| var GenerateCommonPrefix = "skywalking_" |
| |
| var GenerateMethodPrefix = GenerateCommonPrefix + "enhance_" |
| var GenerateVarPrefix = GenerateCommonPrefix + "var_" |
| var OperatorDirs = []string{"operator", "log", "tracing", "tools", "metrics", "profile"} |
| |
| var OperatePrefix = GenerateCommonPrefix + "operator" |
| var TypePrefix = OperatePrefix + "Type" |
| var VarPrefix = OperatePrefix + "Var" |
| var StaticMethodPrefix = OperatePrefix + "StaticMethod" |
| var BeforeInterStart = "beforeInterStart" |
| |
| type Context struct { |
| pkgFullPath string |
| titleCase cases.Caser |
| targetPackage string |
| |
| currentPackageTitle string |
| |
| currentProcessingFile *dst.File |
| |
| packageImport map[string]*rewriteImportInfo |
| rewriteMapping *rewriteMapping |
| |
| InitFuncDetector []string |
| } |
| |
| func NewContext(compilePkgFullPath, targetPackage string) *Context { |
| c := &Context{ |
| pkgFullPath: compilePkgFullPath, |
| titleCase: cases.Title(language.English), |
| targetPackage: targetPackage, |
| packageImport: make(map[string]*rewriteImportInfo), |
| rewriteMapping: newRewriteFuncMapping(make(map[string]string), make(map[string]string)), |
| } |
| // adding self package |
| c.packageImport[targetPackage] = &rewriteImportInfo{ |
| pkgName: targetPackage, |
| isAgentCore: false, |
| ctx: c, |
| } |
| return c |
| } |
| |
| func (c *Context) appendInitFunction(name string) { |
| c.InitFuncDetector = append(c.InitFuncDetector, name) |
| } |
| |
| type rewriteImportInfo struct { |
| pkgName string |
| isAgentCore bool |
| ctx *Context |
| } |
| |
| func (c *Context) IncludeNativeOrReferenceGenerateFiles(content string) error { |
| parseFile, err := decorator.ParseFile(nil, "ref.go", content, parser.ParseComments) |
| if err != nil { |
| return err |
| } |
| |
| dstutil.Apply(parseFile, func(cursor *dstutil.Cursor) bool { |
| switch n := cursor.Node().(type) { |
| case *dst.TypeSpec: |
| c.analyzeNativeOrReferenceFields(cursor.Node(), cursor.Parent(), n.Name.Name) |
| case *dst.FuncDecl: |
| c.analyzeNativeOrReferenceFields(cursor.Node(), cursor.Node(), n.Name.Name) |
| } |
| return true |
| }, func(cursor *dstutil.Cursor) bool { |
| return true |
| }) |
| return nil |
| } |
| |
| func (c *Context) analyzeNativeOrReferenceFields(node, decorationNode dst.Node, currentDeclareName string) { |
| if native := tools.FindDirective(decorationNode, consts.DirectiveNative); native != "" { |
| name, typeName := c.analyzeNativeTypeDirective(native) |
| c.rewriteMapping.addNativeTypeMapping(currentDeclareName, name, typeName) |
| } |
| if reference := tools.FindDirective(decorationNode, consts.DirectiveReferenceGenerate); reference != "" { |
| name, typeName := c.analyzeReferenceGenerateDirective(reference) |
| c.rewriteMapping.addReferenceGenerateMapping(node, currentDeclareName, name, typeName) |
| } |
| } |
| |
| func (c *Context) analyzeNativeTypeDirective(comment string) (packageName, typeName string) { |
| info := strings.SplitN(comment, " ", 3) |
| if len(info) != 3 { |
| panic(fmt.Sprintf("failure to parse the skywalking:native directive: %s", comment)) |
| } |
| return info[1], info[2] |
| } |
| |
| func (c *Context) analyzeReferenceGenerateDirective(comment string) (packageName, typeName string) { |
| info := strings.SplitN(comment, " ", 3) |
| if len(info) != 3 { |
| panic(fmt.Sprintf("failure to parse the skywalking:native directive: %s", comment)) |
| } |
| return info[1], info[2] |
| } |
| |
| func (c *Context) enhanceVarNameWhenRewrite(fieldType dst.Expr) (oldName, replacedName string) { |
| switch t := fieldType.(type) { |
| case *dst.Ident: |
| name := t.Name |
| if c.typeIsBasicTypeValueOrEnhanceName(name) { |
| return "", "" |
| } |
| if mappingName := c.rewriteMapping.findVarMappingName(name); mappingName != "" { |
| t.Name = mappingName |
| return "", "" |
| } |
| // keep the var name help debugging |
| c.rewriteMapping.addVarMapping(name, name) |
| return name, t.Name |
| case *dst.SelectorExpr: |
| return c.enhanceVarNameWhenRewrite(t.X) |
| case *dst.IndexExpr: |
| c.rewriteVarIfExistingMapping(t.Index, t) |
| return c.enhanceVarNameWhenRewrite(t.X) |
| } |
| return "", "" |
| } |
| |
| // nolint |
| func (c *Context) enhanceTypeNameWhenRewrite(fieldType dst.Expr, parent dst.Node, argIndex int) (string, string) { |
| switch t := fieldType.(type) { |
| case *dst.Ident: |
| name := t.Name |
| if c.typeIsBasicTypeValueOrEnhanceName(name) { |
| return "", "" |
| } |
| if c.callIsBasicNamesOrEnhanceName(name) { |
| return "", "" |
| } |
| if mappingName := c.rewriteMapping.findVarMappingName(name); mappingName != "" { |
| t.Name = mappingName |
| return "", "" |
| } |
| if mappingName := c.rewriteMapping.findTypeMappingName(name); mappingName != "" { |
| t.Name = mappingName |
| return "", "" |
| } |
| if native := c.rewriteMapping.findNativeTypeMapping(name); native != nil { |
| // change to the selector expr(package.type) and enhance |
| pkgBase := filepath.Base(native.packageName) |
| // check the package have been import or not |
| c.AddingImportToCurrentFile(pkgBase, native.packageName) |
| c.enhanceTypeNameWhenRewrite(&dst.SelectorExpr{ |
| X: dst.NewIdent(pkgBase), |
| Sel: dst.NewIdent(native.typeName), |
| }, parent, argIndex) |
| return "", "" |
| } |
| // if parent is function call, then the name should be method name |
| if _, ok := parent.(*dst.CallExpr); ok { |
| t.Name = fmt.Sprintf("%s%s%s", StaticMethodPrefix, c.currentPackageTitle, name) |
| } else { |
| if !strings.Contains(name, BeforeInterStart) { |
| t.Name = fmt.Sprintf("%s%s%s", TypePrefix, c.currentPackageTitle, name) |
| } |
| } |
| return name, t.Name |
| case *dst.SelectorExpr: |
| pkgRefName, ok := t.X.(*dst.Ident) |
| if !ok { |
| return c.enhanceTypeNameWhenRewrite(t.X, parent, -1) |
| } |
| // reference by package name |
| var foundPackage *rewriteImportInfo |
| for refImportName, pkgInfo := range c.packageImport { |
| if pkgRefName.Name == refImportName { |
| foundPackage = pkgInfo |
| break |
| } |
| } |
| // if the method call |
| if v := c.rewriteMapping.findVarMappingName(pkgRefName.Name); v != "" { |
| t.X = dst.NewIdent(v) |
| return "", "" |
| } |
| // is the other package reference |
| if strings.HasPrefix(pkgRefName.Name, tools.OtherPackageRefPrefix) { |
| t.X = dst.NewIdent(pkgRefName.Name[len(tools.OtherPackageRefPrefix):]) |
| return "", "" |
| } |
| |
| var generateExpr func() dst.Expr |
| var generateCallExpr func(parent *dst.CallExpr) |
| if foundPackage != nil { |
| generateCallExpr = func(parent *dst.CallExpr) { |
| if c.rewriteVarIfExistingMapping(t.Sel, parent) { |
| if argIndex >= 0 { |
| parent.Args[argIndex] = t.Sel |
| } else { |
| parent.Fun = dst.NewIdent(t.Sel.Name) |
| } |
| } else { |
| parent.Fun = foundPackage.generateStaticMethod(t.Sel.Name) |
| } |
| } |
| generateExpr = func() dst.Expr { |
| return foundPackage.generateType(t.Sel.Name) |
| } |
| } else { |
| // if it cannot found the package, then it just keep the data |
| generateCallExpr = func(parent *dst.CallExpr) { |
| if argIndex >= 0 { |
| parent.Args[argIndex] = fieldType |
| } else { |
| parent.Fun = fieldType |
| } |
| } |
| generateExpr = func() dst.Expr { |
| return fieldType |
| } |
| } |
| |
| switch p := parent.(type) { |
| case *dst.CallExpr: |
| generateCallExpr(p) |
| case *dst.Field: |
| p.Type = generateExpr() |
| case *dst.Ellipsis: |
| p.Elt = generateExpr() |
| case *dst.StarExpr: |
| p.X = generateExpr() |
| case *dst.TypeAssertExpr: |
| p.Type = generateExpr() |
| case *dst.CompositeLit: |
| p.Type = generateExpr() |
| case *dst.ArrayType: |
| p.Elt = generateExpr() |
| case *dst.ValueSpec: |
| p.Type = generateExpr() |
| case *dst.BinaryExpr: |
| if argIndex == 0 { |
| p.X = generateExpr() |
| } else if argIndex == 1 { |
| p.Y = generateExpr() |
| } else { |
| panic("binary expr arg index error") |
| } |
| case *dst.CaseClause: |
| if argIndex < 0 { |
| panic("case clause arg index error") |
| } |
| p.List[argIndex] = generateExpr() |
| } |
| case *dst.StarExpr: |
| return c.enhanceTypeNameWhenRewrite(t.X, t, -1) |
| case *dst.ArrayType: |
| return c.enhanceTypeNameWhenRewrite(t.Elt, t, -1) |
| case *dst.Ellipsis: |
| return c.enhanceTypeNameWhenRewrite(t.Elt, t, -1) |
| case *dst.CompositeLit: |
| for _, elt := range t.Elts { |
| // for struct data, ex: "&xxx{k: v}" |
| if kv, ok := elt.(*dst.KeyValueExpr); ok { |
| c.rewriteVarIfExistingMapping(kv.Value, elt) |
| } else if call, ok := elt.(*dst.CallExpr); ok { |
| c.enhanceTypeNameWhenRewrite(call, t, -1) |
| } |
| } |
| return c.enhanceTypeNameWhenRewrite(t.Type, t, -1) |
| case *dst.UnaryExpr: |
| return c.enhanceTypeNameWhenRewrite(t.X, t, -1) |
| case *dst.CallExpr: |
| for inx, arg := range t.Args { |
| c.enhanceTypeNameWhenRewrite(arg, t, inx) |
| } |
| |
| if id, ok := t.Fun.(*dst.Ident); ok { |
| if c.callIsBasicNamesOrEnhanceName(id.Name) { |
| return "", "" |
| } |
| return c.enhanceTypeNameWhenRewrite(t.Fun, t, -1) |
| } |
| c.enhanceTypeNameWhenRewrite(t.Fun, t, -1) |
| case *dst.FuncType: |
| c.enhanceFuncParameter(t.TypeParams) |
| c.enhanceFuncParameter(t.Params) |
| c.enhanceFuncParameter(t.Results) |
| case *dst.IndexExpr: |
| c.rewriteVarIfExistingMapping(t.Index, t) |
| return c.enhanceTypeNameWhenRewrite(t.X, t, -1) |
| case *dst.TypeAssertExpr: |
| c.enhanceTypeNameWhenRewrite(t.Type, t, -1) |
| return c.enhanceTypeNameWhenRewrite(t.X, t, -1) |
| case *dst.FuncLit: |
| c.enhanceTypeNameWhenRewrite(t.Type, t, -1) |
| for _, stmt := range t.Body.List { |
| c.enhanceFuncStmt(stmt) |
| } |
| case *dst.BinaryExpr: |
| c.enhanceTypeNameWhenRewrite(t.X, t, 0) |
| c.enhanceTypeNameWhenRewrite(t.Y, t, 1) |
| case *dst.ParenExpr: |
| c.rewriteVarIfExistingMapping(t.X, t) |
| case *dst.MapType: |
| c.enhanceTypeNameWhenRewrite(t.Key, t, -1) |
| c.enhanceTypeNameWhenRewrite(t.Value, t, -1) |
| } |
| |
| return "", "" |
| } |
| |
| func (c *Context) typeIsBasicTypeValueOrEnhanceName(name string) bool { |
| if strings.HasPrefix(name, OperatePrefix) || strings.HasPrefix(name, GenerateMethodPrefix) || tools.IsBasicDataType(name) || |
| name == "nil" || name == "true" || name == "false" || name == "append" || name == "panic" || name == "new" || name == "delete" { |
| return true |
| } |
| if _, valErr := strconv.ParseFloat(name, 64); valErr == nil { |
| return true |
| } |
| return false |
| } |
| |
| func (c *Context) alreadyGenerated(name string) bool { |
| return strings.HasPrefix(name, GenerateCommonPrefix) || strings.HasPrefix(name, c.titleCase.String(GenerateCommonPrefix)) |
| } |
| |
| func (c *Context) callIsBasicNamesOrEnhanceName(name string) bool { |
| return strings.HasPrefix(name, OperatePrefix) || strings.HasPrefix(name, GenerateMethodPrefix) || |
| name == "make" || name == "recover" || name == "len" |
| } |
| |
| func (r *rewriteImportInfo) generateStaticMethod(name string) *dst.Ident { |
| if r.ctx.typeIsBasicTypeValueOrEnhanceName(name) { |
| return dst.NewIdent(name) |
| } |
| if r.isAgentCore { |
| return dst.NewIdent(fmt.Sprintf("%s%s%s", StaticMethodPrefix, r.ctx.titleCase.String(r.pkgName), name)) |
| } |
| return dst.NewIdent(name) |
| } |
| |
| func (r *rewriteImportInfo) generateType(name string) *dst.Ident { |
| if r.isAgentCore { |
| return dst.NewIdent(fmt.Sprintf("%s%s%s", TypePrefix, r.ctx.titleCase.String(r.pkgName), name)) |
| } |
| return dst.NewIdent(name) |
| } |
| |
| type rewriteMapping struct { |
| // push or pop the names when the block statement is called |
| rewriteVarNames []map[string]string |
| rewriteTypeNames []map[string]string |
| nativeTypes map[string]*nativeType |
| } |
| |
| type nativeType struct { |
| packageName string |
| typeName string |
| } |
| |
| func newRewriteFuncMapping(varNames, typeNames map[string]string) *rewriteMapping { |
| return &rewriteMapping{ |
| rewriteVarNames: []map[string]string{varNames}, |
| rewriteTypeNames: []map[string]string{typeNames}, |
| nativeTypes: map[string]*nativeType{}, |
| } |
| } |
| |
| func (m *rewriteMapping) addVarMapping(key, value string) { |
| m.rewriteVarNames[len(m.rewriteVarNames)-1][key] = value |
| } |
| |
| func (m *rewriteMapping) addTypeMapping(key, value string) { |
| m.rewriteTypeNames[len(m.rewriteTypeNames)-1][key] = value |
| } |
| |
| func (m *rewriteMapping) addNativeTypeMapping(key, packageName, typeName string) { |
| m.nativeTypes[key] = &nativeType{ |
| packageName: packageName, |
| typeName: typeName, |
| } |
| } |
| |
| func (m *rewriteMapping) addReferenceGenerateMapping(node dst.Node, key, packageName, typeName string) { |
| titleCase := cases.Title(language.English) |
| packageTitle := titleCase.String(filepath.Base(packageName)) |
| switch node.(type) { |
| case *dst.FuncDecl: |
| m.addNativeTypeMapping(key, packageName, |
| fmt.Sprintf("%s%s%s", titleCase.String(GenerateMethodPrefix), packageTitle, typeName)) |
| case *dst.TypeSpec: |
| m.addNativeTypeMapping(key, packageName, |
| fmt.Sprintf("%s%s%s", titleCase.String(TypePrefix), packageTitle, typeName)) |
| } |
| } |
| |
| func (m *rewriteMapping) pushBlockStack() { |
| m.rewriteVarNames = append(m.rewriteVarNames, make(map[string]string)) |
| m.rewriteTypeNames = append(m.rewriteTypeNames, make(map[string]string)) |
| } |
| |
| func (m *rewriteMapping) popBlockStack() { |
| m.rewriteVarNames = m.rewriteVarNames[:len(m.rewriteVarNames)-1] |
| m.rewriteTypeNames = m.rewriteTypeNames[:len(m.rewriteTypeNames)-1] |
| } |
| |
| func (m *rewriteMapping) findVarMappingName(name string) string { |
| for i := len(m.rewriteVarNames) - 1; i >= 0; i-- { |
| if v, ok := m.rewriteVarNames[i][name]; ok { |
| return v |
| } |
| } |
| return "" |
| } |
| |
| func (m *rewriteMapping) findTypeMappingName(name string) string { |
| for i := len(m.rewriteTypeNames) - 1; i >= 0; i-- { |
| if v, ok := m.rewriteTypeNames[i][name]; ok { |
| return v |
| } |
| } |
| return "" |
| } |
| |
| func (m *rewriteMapping) findNativeTypeMapping(name string) *nativeType { |
| return m.nativeTypes[name] |
| } |