Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions demo/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ type MyStruct struct{}

func (m *MyStruct) Example() { println("MyStruct.Example") }

type GenStruct[T any] struct {
Value T
}

func (m *GenStruct[T]) GenericRecvExample(t T) T {
fmt.Printf("%s%s\n", m.Value, t)
return t
}

func GenericExample[K comparable, V any](key K, value V) V {
println("Hello, Generic World!", key, value)
return value
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test case func (recv *Recv[T]) GenericExample

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test added.

There were some extra changes needed to support this case:

  • Extract receiver type parameters
  • Set hook ctx params as a combination of receiver and function types
  • Preserve generics in trampolines

This new code would require some new 👀 63a9420

// Example demonstrates how to use the instrumenter.
func Example() {
// Output:
Expand Down Expand Up @@ -58,6 +72,10 @@ func main() {
m.NewField = "abc"
m.Example()

_ = GenericExample(1, 2)
g := &GenStruct[string]{Value: "Hello"}
_ = g.GenericRecvExample(", Generic Recv World!")

// Call real module function
println(rate.Every(time.Duration(1)))
}
28 changes: 28 additions & 0 deletions pkg/instrumentation/helloworld/helloworld_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,32 @@ func MyHook1After(ictx inst.HookContext) {
println("After MyStruct.Example()")
}

func MyHookRecvBefore(ictx inst.HookContext, recv, _ interface{}) {
println("GenericRecvExample before hook")
}

func MyHookRecvAfter(ictx inst.HookContext, _ interface{}) {
println("GenericRecvExample after hook")
}

func MyHookGenericBefore(ictx inst.HookContext, _, _ interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add usages of HookContext methods inside Hook function to exercise their functionalities

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done in 74ff92a

These integration tests should be refactored once golden file approach is moved to /test/integration

println("GenericExample before hook")
fmt.Printf("[Generic] Function: %s.%s\n", ictx.GetPackageName(), ictx.GetFuncName())
fmt.Printf("[Generic] Param count: %d\n", ictx.GetParamCount())
fmt.Printf("[Generic] Skip call: %v\n", ictx.IsSkipCall())
for i := 0; i < ictx.GetParamCount(); i++ {
fmt.Printf("[Generic] Param[%d]: %v\n", i, *ictx.GetParam(i).(*int))
}
ictx.SetData("test-data")
}

func MyHookGenericAfter(ictx inst.HookContext, _ interface{}) {
println("GenericExample after hook")
fmt.Printf("[Generic] Data from Before: %v\n", ictx.GetData())
fmt.Printf("[Generic] Return value count: %d\n", ictx.GetReturnValCount())
for i := 0; i < ictx.GetReturnValCount(); i++ {
fmt.Printf("[Generic] Return[%d]: %v\n", i, *ictx.GetReturnVal(i).(*int))
}
}

func BeforeUnderscore(ictx inst.HookContext, _ int, _ float32) {}
25 changes: 25 additions & 0 deletions test/integration/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ func TestBasic(t *testing.T) {
"Every1",
"Every3",
"MyStruct.Example",
"GenericExample before hook",
"Hello, Generic World! 1 2",
"GenericExample after hook",
"traceID: 123, spanID: 456",
"GenericRecvExample before hook",
"Hello, Generic Recv World!",
"GenericRecvExample after hook",
"traceID: 123, spanID: 456",
"[MyHook]",
"=setupOpenTelemetry=",
Expand All @@ -31,4 +38,22 @@ func TestBasic(t *testing.T) {
for _, e := range expect {
require.Contains(t, output, e)
}

verifyGenericHookContextLogs(t, output)
}

func verifyGenericHookContextLogs(t *testing.T, output string) {
expectedGenericLogs := []string{
"[Generic] Function: main.GenericExample",
"[Generic] Param count: 2",
"[Generic] Skip call: false",
"[Generic] Param[0]: 1",
"[Generic] Param[1]: 2",
"[Generic] Data from Before: test-data",
"[Generic] Return value count: 1",
"[Generic] Return[0]: 2",
}
for _, log := range expectedGenericLogs {
require.Contains(t, output, log, "Expected generic HookContext log: %s", log)
}
}
15 changes: 15 additions & 0 deletions tool/data/helloworld.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,18 @@ underscore_param:
func: Underscore
before: BeforeUnderscore
path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld"

hook_generic:
target: main
func: GenericExample
before: MyHookGenericBefore
after: MyHookGenericAfter
path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld"

hook_generic_recv:
target: main
func: GenericRecvExample
recv: "*GenStruct"
before: MyHookRecvBefore
after: MyHookRecvAfter
path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld"
45 changes: 43 additions & 2 deletions tool/internal/ast/primitives.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,31 @@ func AddressOf(name string) *dst.UnaryExpr {
return &dst.UnaryExpr{Op: token.AND, X: Ident(name)}
}

func CallTo(name string, args []dst.Expr) *dst.CallExpr {
// CallTo creates a call expression to a function with optional type arguments for generics.
// For non-generic functions (typeArgs is nil or empty), creates a simple call: Foo(args...)
// For generic functions with type arguments, creates: Foo[T1, T2](args...)
func CallTo(name string, typeArgs *dst.FieldList, args []dst.Expr) *dst.CallExpr {
if typeArgs == nil || len(typeArgs.List) == 0 {
return &dst.CallExpr{
Fun: &dst.Ident{Name: name},
Args: args,
}
}

var indices []dst.Expr
for _, field := range typeArgs.List {
for _, ident := range field.Names {
indices = append(indices, Ident(ident.Name))
}
}
var fun dst.Expr
if len(indices) == 1 {
fun = IndexExpr(Ident(name), indices[0])
} else {
fun = IndexListExpr(Ident(name), indices)
}
return &dst.CallExpr{
Fun: &dst.Ident{Name: name},
Fun: fun,
Args: args,
}
}
Expand Down Expand Up @@ -103,6 +125,14 @@ func IndexExpr(x, index dst.Expr) *dst.IndexExpr {
}
}

func IndexListExpr(x dst.Expr, indices []dst.Expr) *dst.IndexListExpr {
e := util.AssertType[dst.Expr](dst.Clone(x))
return &dst.IndexListExpr{
X: e,
Indices: indices,
}
}

func TypeAssertExpr(x, t dst.Expr) *dst.TypeAssertExpr {
e := util.AssertType[dst.Expr](dst.Clone(t))
return &dst.TypeAssertExpr{
Expand Down Expand Up @@ -275,3 +305,14 @@ func StructLit(typeName string, fields ...*dst.KeyValueExpr) dst.Expr {
X: CompositeLit(Ident(typeName), exprs),
}
}

// CloneTypeParams safely clones a type parameter field list for generic functions.
// Returns nil if the input is nil.
func CloneTypeParams(typeParams *dst.FieldList) *dst.FieldList {
if typeParams == nil {
return nil
}
cloned, ok := dst.Clone(typeParams).(*dst.FieldList)
util.Assert(ok, "typeParams is not a FieldList")
return cloned
}
Loading