blob: 2b67d3336e7e36da0f21f882d8c93658ae22e678 [file] [log] [blame]
// 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 plugins
import (
"fmt"
"path/filepath"
"strings"
"github.com/apache/skywalking-go/plugins/core/instrument"
"github.com/apache/skywalking-go/tools/go-agent/instrument/agentcore"
"github.com/apache/skywalking-go/tools/go-agent/instrument/plugins/rewrite"
"github.com/apache/skywalking-go/tools/go-agent/tools"
"github.com/dave/dst"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var methodEnhanceAdapterFiles = make(map[string]bool)
var methodImportAgentCorePackages = []string{
"log", "tracing", "operator",
}
type MethodEnhance struct {
funcDecl *dst.FuncDecl
path string
packageName string
fullPackage string
InstrumentName string
InterceptorDefineName string
InterceptorGeneratedName string
InterceptorVarName string
Parameters []*tools.PackagedParameterInfo
Recvs []*tools.PackagedParameterInfo
Results []*tools.PackagedParameterInfo
FuncID string
AdapterPreFuncName string
AdapterPostFuncName string
replacementKey string
replacementValue string
importAnalyzer *tools.ImportAnalyzer
}
func NewMethodEnhance(inst instrument.Instrument, matcher *instrument.Point, f *dst.FuncDecl, path string,
importAnalyzer *tools.ImportAnalyzer) *MethodEnhance {
fullPackage := filepath.Join(inst.BasePackage(), matcher.PackagePath)
pkgName := filepath.Base(fullPackage)
if matcher.PackageName != "" {
pkgName = matcher.PackageName
}
enhance := &MethodEnhance{
funcDecl: f,
path: path,
fullPackage: fullPackage,
packageName: pkgName,
InstrumentName: inst.Name(),
InterceptorDefineName: matcher.Interceptor,
Parameters: tools.EnhanceParameterNamesWithPackagePrefix(pkgName, f.Type.Params, false),
Results: tools.EnhanceParameterNamesWithPackagePrefix(pkgName, f.Type.Results, true),
}
if f.Recv != nil {
enhance.Recvs = tools.EnhanceParameterNamesWithPackagePrefix(pkgName, f.Recv, false)
}
importAnalyzer.AnalyzeNeedsImports(path, f.Type.Params)
importAnalyzer.AnalyzeNeedsImports(path, f.Type.Results)
enhance.importAnalyzer = importAnalyzer
enhance.FuncID = tools.BuildFuncIdentity(filepath.Join(inst.BasePackage(), matcher.PackagePath), f)
enhance.AdapterPreFuncName = fmt.Sprintf("%s%s", rewrite.GenerateMethodPrefix, enhance.FuncID)
enhance.AdapterPostFuncName = fmt.Sprintf("%s%s_ret", rewrite.GenerateMethodPrefix, enhance.FuncID)
// the interceptor name needs to add the function id ensure there no conflict in the framework package
titleCase := cases.Title(language.English)
packageTitle := filepath.Base(titleCase.String(filepath.Join(inst.BasePackage(), pkgName)))
enhance.InterceptorGeneratedName = fmt.Sprintf("%s%s%s", rewrite.TypePrefix, packageTitle, enhance.InterceptorDefineName)
enhance.InterceptorVarName = fmt.Sprintf("%sinterceptor_%s", rewrite.GenerateVarPrefix, enhance.FuncID)
return enhance
}
func (m *MethodEnhance) PackageName() string {
return m.packageName
}
func (m *MethodEnhance) BuildForInvoker() {
insertsTmpl, err := templatesFS.ReadFile("templates/method_inserts.tmpl")
if err != nil {
panic(fmt.Errorf("reading method inserts: %w", err))
}
result := tools.ExecuteTemplate(string(insertsTmpl), m)
m.replacementKey = fmt.Sprintf("//goagent:enhance_%s\n", m.FuncID)
m.replacementValue = result
m.funcDecl.Body.Decs.Lbrace.Prepend("\n", m.replacementKey)
}
func (m *MethodEnhance) BuildImports(decl *dst.GenDecl) {
if !methodEnhanceAdapterFiles[filepath.Dir(m.path)] {
for _, n := range methodImportAgentCorePackages {
m.appendImport(decl, "", fmt.Sprintf("%s/%s", agentcore.EnhanceFromBasePackage, n))
}
m.appendImport(decl, m.packageName, m.fullPackage)
methodEnhanceAdapterFiles[filepath.Dir(m.path)] = true
}
m.importAnalyzer.AppendUsedImports(decl)
}
func (m *MethodEnhance) appendImport(decl *dst.GenDecl, name, path string) {
imp := &dst.ImportSpec{
Path: &dst.BasicLit{
Value: fmt.Sprintf("%q", path),
},
}
if name != "" {
imp.Name = dst.NewIdent(name)
}
decl.Specs = append(decl.Specs, imp)
}
func (m *MethodEnhance) BuildForDelegator() []dst.Decl {
result := make([]dst.Decl, 0)
result = append(result, tools.GoStringToDecls(fmt.Sprintf(`var %s = &%s{}`, m.InterceptorVarName, m.InterceptorGeneratedName))...)
preFunc := &dst.FuncDecl{
Name: &dst.Ident{Name: m.AdapterPreFuncName},
Type: &dst.FuncType{
Params: &dst.FieldList{},
Results: &dst.FieldList{},
},
}
for i, recv := range m.Recvs {
preFunc.Type.Params.List = append(preFunc.Type.Params.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("recv_%d", i))},
Type: &dst.StarExpr{X: recv.PackagedType()},
})
}
for i, parameter := range m.Parameters {
preFunc.Type.Params.List = append(preFunc.Type.Params.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("param_%d", i))},
Type: &dst.StarExpr{X: m.changeTypeIfNeeds(parameter.PackagedType())},
})
}
for i, result := range m.Results {
preFunc.Type.Results.List = append(preFunc.Type.Results.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("ret_%d", i))},
Type: result.PackagedType(),
})
}
preFunc.Type.Results.List = append(preFunc.Type.Results.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent("inv")},
Type: &dst.StarExpr{X: &dst.SelectorExpr{X: dst.NewIdent("operator"), Sel: dst.NewIdent("realInvocation")}},
}, &dst.Field{
Names: []*dst.Ident{dst.NewIdent("skip")},
Type: dst.NewIdent("bool"),
})
beforeFile, err := templatesFS.ReadFile("templates/method_intercept_before.tmpl")
if err != nil {
panic(fmt.Errorf("reading method before intercept template failure: %w", err))
}
preFunc.Body = &dst.BlockStmt{
List: tools.GoStringToStats(tools.ExecuteTemplate(string(beforeFile), m)),
}
result = append(result, preFunc)
postFunc := &dst.FuncDecl{
Name: &dst.Ident{Name: m.AdapterPostFuncName},
Type: &dst.FuncType{
Params: &dst.FieldList{},
Results: &dst.FieldList{},
},
}
postFunc.Type.Params.List = append(postFunc.Type.Params.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent("invocation")},
Type: &dst.StarExpr{X: &dst.SelectorExpr{X: dst.NewIdent("operator"), Sel: dst.NewIdent("realInvocation")}},
})
for inx, f := range m.Results {
postFunc.Type.Params.List = append(postFunc.Type.Params.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("ret_%d", inx))},
Type: &dst.StarExpr{X: f.PackagedType()},
})
}
afterFile, err := templatesFS.ReadFile("templates/method_intercept_after.tmpl")
if err != nil {
panic(fmt.Errorf("reading method after intercept template failure: %w", err))
}
postFunc.Body = &dst.BlockStmt{
List: tools.GoStringToStats(tools.ExecuteTemplate(string(afterFile), m)),
}
result = append(result, postFunc)
return result
}
func (m *MethodEnhance) changeTypeIfNeeds(tp dst.Expr) dst.Expr {
// change "...XXX" to "[]XXX" for reference type
if el, ok := tp.(*dst.Ellipsis); ok {
return &dst.ArrayType{Elt: el.Elt}
}
return tp
}
func (m *MethodEnhance) ReplaceFileContent(path, content string) string {
if m.path == path {
return strings.Replace(content, m.replacementKey, m.replacementValue, 1)
}
return content
}