tool/resource/bundle.go (151 lines of code) (raw):
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// 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 resource
import (
"encoding/json"
"fmt"
"path/filepath"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util"
"github.com/dave/dst"
)
const (
MatchedRulesJsonFile = "matched_rules.json"
)
// RuleBundle is a collection of rules that matched with one compilation action
type RuleBundle struct {
PackageName string
ImportPath string
FileRules []*InstFileRule
File2FuncRules map[string]map[string][]*InstFuncRule
File2StructRules map[string]map[string][]*InstStructRule
}
func NewRuleBundle(importPath string) *RuleBundle {
return &RuleBundle{
PackageName: "",
ImportPath: importPath,
FileRules: make([]*InstFileRule, 0),
File2FuncRules: make(map[string]map[string][]*InstFuncRule),
File2StructRules: make(map[string]map[string][]*InstStructRule),
}
}
func (rb *RuleBundle) String() string {
bs, _ := json.Marshal(rb)
return string(bs)
}
func (rb *RuleBundle) IsValid() bool {
return rb != nil &&
(len(rb.FileRules) > 0 ||
len(rb.File2FuncRules) > 0 ||
len(rb.File2StructRules) > 0)
}
func (rb *RuleBundle) AddFile2FuncRule(file string, rule *InstFuncRule) error {
file, err := filepath.Abs(file)
if err != nil {
return errc.New(errc.ErrAbsPath, err.Error())
}
fn := rule.Function + "," + rule.ReceiverType
util.Assert(fn != "", "sanity check")
if _, exist := rb.File2FuncRules[file]; !exist {
rb.File2FuncRules[file] = make(map[string][]*InstFuncRule)
rb.File2FuncRules[file][fn] = []*InstFuncRule{rule}
} else {
rb.File2FuncRules[file][fn] =
append(rb.File2FuncRules[file][fn], rule)
}
return nil
}
func (rb *RuleBundle) AddFile2StructRule(file string, rule *InstStructRule) error {
file, err := filepath.Abs(file)
if err != nil {
return errc.New(errc.ErrAbsPath, err.Error())
}
st := rule.StructType
util.Assert(st != "", "sanity check")
if _, exist := rb.File2StructRules[file]; !exist {
rb.File2StructRules[file] = make(map[string][]*InstStructRule)
rb.File2StructRules[file][st] = []*InstStructRule{rule}
} else {
rb.File2StructRules[file][st] =
append(rb.File2StructRules[file][st], rule)
}
return nil
}
func (rb *RuleBundle) SetPackageName(name string) {
rb.PackageName = name
}
func (rb *RuleBundle) AddFileRule(rule *InstFileRule) {
rb.FileRules = append(rb.FileRules, rule)
}
func isHookDefined(root *dst.File, rule *InstFuncRule) bool {
util.Assert(rule.OnEnter != "" || rule.OnExit != "", "hook must be set")
if rule.OnEnter != "" {
if util.FindFuncDecl(root, rule.OnEnter) == nil {
return false
}
}
if rule.OnExit != "" {
if util.FindFuncDecl(root, rule.OnExit) == nil {
return false
}
}
return true
}
func FindHookFile(rule *InstFuncRule) (string, error) {
files, err := FindRuleFiles(rule)
if err != nil {
return "", err
}
for _, file := range files {
if !util.IsGoFile(file) {
continue
}
root, err := util.ParseAstFromFileFast(file)
if err != nil {
return "", err
}
if isHookDefined(root, rule) {
return file, nil
}
}
return "", errc.New(errc.ErrNotExist,
fmt.Sprintf("no hook %s/%s found for %s from %v",
rule.OnEnter, rule.OnExit, rule.Function, files))
}
func FindRuleFiles(rule InstRule) ([]string, error) {
files, err := util.ListFiles(rule.GetPath())
if err != nil {
return nil, err
}
switch rule.(type) {
case *InstFuncRule, *InstFileRule:
return files, nil
case *InstStructRule:
util.ShouldNotReachHereT("insane rule type")
}
return nil, nil
}
func StoreRuleBundles(bundles []*RuleBundle) error {
util.GuaranteeInPreprocess()
ruleFile := util.GetPreprocessLogPath(MatchedRulesJsonFile)
bs, err := json.Marshal(bundles)
if err != nil {
return errc.New(errc.ErrInvalidJSON, err.Error())
}
_, err = util.WriteFile(ruleFile, string(bs))
if err != nil {
return err
}
return nil
}
func LoadRuleBundles() ([]*RuleBundle, error) {
util.GuaranteeInInstrument()
ruleFile := util.GetPreprocessLogPath(MatchedRulesJsonFile)
data, err := util.ReadFile(ruleFile)
if err != nil {
return nil, err
}
var bundles []*RuleBundle
err = json.Unmarshal([]byte(data), &bundles)
if err != nil {
return nil, errc.New(errc.ErrInvalidJSON, "bad "+ruleFile)
}
return bundles, nil
}