From bed3e7999fc380a619874a505a0c55efcf520777 Mon Sep 17 00:00:00 2001 From: Bill Havanki Date: Thu, 12 Aug 2021 14:57:44 -0400 Subject: [PATCH] Fix use of reflect.StringHeader in prometheus/metric.go (#133) The `go vet` command in Go 1.16 reports a warning for inappropriate use of reflect.StringHeader. https://github.com/golang/go/issues/40701 Its use in prometheus/metric.go to convert a byte array to a string in place began to trigger the warning. That code has been replaced with a safer variant that avoids the `vet` warning and still converts the array without allocating new memory. https://stackoverflow.com/a/66865482 Additionally, the CircleCI test now uses a pinned influxdb image of 1.8.9. The 2.x influxdb Docker images require authentication to use. Co-authored-by: Collin Van Dyck --- .circleci/config.yml | 2 +- prometheus/metric.go | 16 +++++++++++---- prometheus/metric_test.go | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38a618c..abc23d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ jobs: working_directory: /go/src/github.com/segmentio/stats docker: - image: circleci/golang - - image: influxdb:alpine + - image: influxdb:1.8.9-alpine ports: ['8086:8086'] steps: - checkout diff --git a/prometheus/metric.go b/prometheus/metric.go index a971c86..597ec49 100644 --- a/prometheus/metric.go +++ b/prometheus/metric.go @@ -401,11 +401,19 @@ func le(buckets []stats.Value) string { } b = appendFloat(b, valueOf(v)) } + return unsafeByteSliceToString(b) +} - return *(*string)(unsafe.Pointer(&reflect.StringHeader{ - Data: uintptr(unsafe.Pointer(&b[0])), - Len: len(b), - })) +// This function converts the byte array to a string without additional +// memory allocation. +// Source: https://stackoverflow.com/a/66865482 (license: CC BY-SA 4.0) +func unsafeByteSliceToString(b []byte) string { + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + var s string + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + sh.Data = sliceHeader.Data + sh.Len = sliceHeader.Len + return s } func nextLe(s string) (head string, tail string) { diff --git a/prometheus/metric_test.go b/prometheus/metric_test.go index 98af556..afdb22e 100644 --- a/prometheus/metric_test.go +++ b/prometheus/metric_test.go @@ -11,6 +11,47 @@ import ( "github.com/segmentio/stats/v4" ) +func TestUnsafeByteSliceToString(t *testing.T) { + for _, test := range []struct { + name string + input []byte + expected string + }{ + { + name: "nil bytes", + input: nil, + expected: "", + }, + { + name: "no bytes", + input: []byte{}, + expected: "", + }, + { + name: "list of floats", + input: []byte("1.2:3.4:5.6:7.8"), + expected: "1.2:3.4:5.6:7.8", + }, + { + name: "deadbeef", + input: []byte{0xde, 0xad, 0xbe, 0xef}, + expected: "\xde\xad\xbe\xef", + }, + { + name: "embedded zero", + input: []byte("this\x00that"), + expected: "this\x00that", + }, + } { + t.Run(test.name, func(t *testing.T) { + res := unsafeByteSliceToString(test.input) + if res != test.expected { + t.Errorf("Expected %q but got %q", test.expected, res) + } + }) + } +} + func TestMetricStore(t *testing.T) { input := []metric{ {mtype: counter, scope: "test", name: "A", value: 1},