Files
daa5bf7ea2 [exporter/debug] Guard from out of bounds profiles dictionary indices (#14804)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description
check before accessing profiles dictionary indices and fail gracefully
if they are out of bounds
<!-- Issue number if applicable -->
#### Link to tracking issue
Fixes #14803

<!--Describe what testing was performed and which tests were added.-->
#### Testing
Added tests to prove missing dictionary indices fail and don't panic
<!--Describe the documentation added.-->
#### Documentation

<!--Please delete paragraphs that you did not use before submitting.-->

---------

Co-authored-by: Andrzej Stencel <[email protected]>
2026-04-06 23:16:53 +00:00

176 lines
5.0 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestProfilesText(t *testing.T) {
tests := []struct {
name string
in pprofile.Profiles
out string
}{
{
name: "empty_profiles",
in: pprofile.NewProfiles(),
out: "empty.out",
},
{
name: "two_profiles",
in: extendProfiles(testdata.GenerateProfiles(2)),
out: "two_profiles.out",
},
{
name: "profiles_with_entity_refs",
in: generateProfilesWithEntityRefs(),
out: "profiles_with_entity_refs.out",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextProfilesMarshaler().MarshalProfiles(tt.in)
require.NoError(t, err)
out, err := os.ReadFile(filepath.Join("testdata", "profiles", tt.out))
require.NoError(t, err)
expected := strings.ReplaceAll(string(out), "\r", "")
assert.Equal(t, expected, string(got))
})
}
}
func TestProfilesTextInvalidDictionaryIndex(t *testing.T) {
tests := []struct {
name string
in pprofile.Profiles
expectedContains string
}{
{
name: "invalid_attribute_index",
in: func() pprofile.Profiles {
profiles := pprofile.NewProfiles()
dic := profiles.Dictionary()
dic.StringTable().Append("")
profile := profiles.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
sample := profile.Samples().AppendEmpty()
sample.Values().Append(100)
// Attribute index 99 is out of bounds (attribute table is empty)
sample.AttributeIndices().Append(99)
return profiles
}(),
expectedContains: "[Missing Dictionary AttributeTable Item #99]",
},
{
name: "invalid_string_index",
in: func() pprofile.Profiles {
profiles := pprofile.NewProfiles()
dic := profiles.Dictionary()
dic.StringTable().Append("")
a := dic.AttributeTable().AppendEmpty()
// KeyStrindex 99 is out of bounds
a.SetKeyStrindex(99)
a.Value().SetStr("value1")
profile := profiles.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
sample := profile.Samples().AppendEmpty()
sample.Values().Append(100)
sample.AttributeIndices().Append(0)
return profiles
}(),
expectedContains: "[Missing Dictionary Key String Item #99]",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextProfilesMarshaler().MarshalProfiles(tt.in)
require.NoError(t, err)
assert.Contains(t, string(got), tt.expectedContains)
})
}
}
// GenerateExtendedProfiles generates dummy profiling data with extended values for tests
func extendProfiles(profiles pprofile.Profiles) pprofile.Profiles {
dic := profiles.Dictionary()
location := dic.LocationTable().AppendEmpty()
location.SetMappingIndex(3)
location.SetAddress(4)
line := location.Lines().AppendEmpty()
line.SetFunctionIndex(1)
line.SetLine(2)
line.SetColumn(3)
location.AttributeIndices().FromRaw([]int32{6, 7})
dic.StringTable().Append("intValue")
at := dic.AttributeTable()
a := at.AppendEmpty()
a.SetKeyStrindex(1)
a.Value().SetInt(42)
attributeUnits := dic.AttributeTable().AppendEmpty()
attributeUnits.SetKeyStrindex(2)
attributeUnits.SetUnitStrindex(5)
dic.StringTable().Append("foobar")
mapping := dic.MappingTable().AppendEmpty()
mapping.SetMemoryStart(2)
mapping.SetMemoryLimit(3)
mapping.SetFileOffset(4)
mapping.SetFilenameStrindex(5)
mapping.AttributeIndices().FromRaw([]int32{7, 8})
function := dic.FunctionTable().AppendEmpty()
function.SetNameStrindex(2)
function.SetSystemNameStrindex(3)
function.SetFilenameStrindex(4)
function.SetStartLine(5)
linkTable := dic.LinkTable().AppendEmpty()
linkTable.SetTraceID([16]byte{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
linkTable.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
sc := profiles.ResourceProfiles().At(0).ScopeProfiles().At(0)
profilesCount := profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).Profiles().Len()
for i := range profilesCount {
if i%2 == 0 {
profile := sc.Profiles().At(i)
profile.AttributeIndices().FromRaw([]int32{1})
}
}
return profiles
}
func generateProfilesWithEntityRefs() pprofile.Profiles {
pd := pprofile.NewProfiles()
rp := pd.ResourceProfiles().AppendEmpty()
setupResourceWithEntityRefs(rp.Resource())
sp := rp.ScopeProfiles().AppendEmpty()
sp.Scope().SetName("test-scope")
profile := sp.Profiles().AppendEmpty()
sample := profile.Samples().AppendEmpty()
sample.Values().Append(100)
dic := pd.Dictionary()
dic.StringTable().Append("")
dic.StringTable().Append("cpu")
dic.StringTable().Append("nanoseconds")
sampleType := profile.SampleType()
sampleType.SetTypeStrindex(1)
sampleType.SetUnitStrindex(2)
return pd
}