diff --git a/CHANGELOG.md b/CHANGELOG.md index c0188ab36f0..f069c07bf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggragation. (#3408) - Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432) - Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440) +- Prevents panic when using incorrect `attribute.Value.As[Type]Slice()`. (#3489) - Prevent duplicate Prometheus description, unit, and type. (#3469) ## Removed diff --git a/attribute/kv_test.go b/attribute/kv_test.go index e819d404a2f..17815224ad1 100644 --- a/attribute/kv_test.go +++ b/attribute/kv_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) @@ -132,3 +133,50 @@ func TestKeyValueValid(t *testing.T) { } } } + +func TestIncorrectCast(t *testing.T) { + testCases := []struct { + name string + val attribute.Value + }{ + { + name: "Float64", + val: attribute.Float64Value(1.0), + }, + { + name: "Int64", + val: attribute.Int64Value(2), + }, + { + name: "String", + val: attribute.BoolValue(true), + }, + { + name: "Float64Slice", + val: attribute.Float64SliceValue([]float64{1.0}), + }, + { + name: "Int64Slice", + val: attribute.Int64SliceValue([]int64{2}), + }, + { + name: "StringSlice", + val: attribute.BoolSliceValue([]bool{true}), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + assert.NotPanics(t, func() { + tt.val.AsBool() + tt.val.AsBoolSlice() + tt.val.AsFloat64() + tt.val.AsFloat64Slice() + tt.val.AsInt64() + tt.val.AsInt64Slice() + tt.val.AsInterface() + tt.val.AsString() + tt.val.AsStringSlice() + }) + }) + } +} diff --git a/attribute/value.go b/attribute/value.go index 80a37bd6ff3..34a4e548dd1 100644 --- a/attribute/value.go +++ b/attribute/value.go @@ -142,6 +142,13 @@ func (v Value) AsBool() bool { // AsBoolSlice returns the []bool value. Make sure that the Value's type is // BOOLSLICE. func (v Value) AsBoolSlice() []bool { + if v.vtype != BOOLSLICE { + return nil + } + return v.asBoolSlice() +} + +func (v Value) asBoolSlice() []bool { return attribute.AsSlice[bool](v.slice) } @@ -154,6 +161,13 @@ func (v Value) AsInt64() int64 { // AsInt64Slice returns the []int64 value. Make sure that the Value's type is // INT64SLICE. func (v Value) AsInt64Slice() []int64 { + if v.vtype != INT64SLICE { + return nil + } + return v.asInt64Slice() +} + +func (v Value) asInt64Slice() []int64 { return attribute.AsSlice[int64](v.slice) } @@ -166,6 +180,13 @@ func (v Value) AsFloat64() float64 { // AsFloat64Slice returns the []float64 value. Make sure that the Value's type is // FLOAT64SLICE. func (v Value) AsFloat64Slice() []float64 { + if v.vtype != FLOAT64SLICE { + return nil + } + return v.asFloat64Slice() +} + +func (v Value) asFloat64Slice() []float64 { return attribute.AsSlice[float64](v.slice) } @@ -178,6 +199,13 @@ func (v Value) AsString() string { // AsStringSlice returns the []string value. Make sure that the Value's type is // STRINGSLICE. func (v Value) AsStringSlice() []string { + if v.vtype != STRINGSLICE { + return nil + } + return v.asStringSlice() +} + +func (v Value) asStringSlice() []string { return attribute.AsSlice[string](v.slice) } @@ -189,19 +217,19 @@ func (v Value) AsInterface() interface{} { case BOOL: return v.AsBool() case BOOLSLICE: - return v.AsBoolSlice() + return v.asBoolSlice() case INT64: return v.AsInt64() case INT64SLICE: - return v.AsInt64Slice() + return v.asInt64Slice() case FLOAT64: return v.AsFloat64() case FLOAT64SLICE: - return v.AsFloat64Slice() + return v.asFloat64Slice() case STRING: return v.stringly case STRINGSLICE: - return v.AsStringSlice() + return v.asStringSlice() } return unknownValueType{} } @@ -210,19 +238,19 @@ func (v Value) AsInterface() interface{} { func (v Value) Emit() string { switch v.Type() { case BOOLSLICE: - return fmt.Sprint(v.AsBoolSlice()) + return fmt.Sprint(v.asBoolSlice()) case BOOL: return strconv.FormatBool(v.AsBool()) case INT64SLICE: - return fmt.Sprint(v.AsInt64Slice()) + return fmt.Sprint(v.asInt64Slice()) case INT64: return strconv.FormatInt(v.AsInt64(), 10) case FLOAT64SLICE: - return fmt.Sprint(v.AsFloat64Slice()) + return fmt.Sprint(v.asFloat64Slice()) case FLOAT64: return fmt.Sprint(v.AsFloat64()) case STRINGSLICE: - return fmt.Sprint(v.AsStringSlice()) + return fmt.Sprint(v.asStringSlice()) case STRING: return v.stringly default: