diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 4ce1f40b..10529300 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -194,6 +194,7 @@ func (p *TextParser) reset(in io.Reader) { // start of a line (or whitespace leading up to it). func (p *TextParser) startOfLine() stateFn { p.lineCount++ + p.currentMetric = nil p.currentMetricIsInsideBraces = false p.currentMetricInsideBracesIsPresent = false if p.skipBlankTab(); p.err != nil { @@ -339,12 +340,11 @@ func (p *TextParser) startLabelName() stateFn { return nil // Unexpected end of input. } if p.currentByte == '}' { - if p.currentMF == nil { + if p.currentMetric == nil { // The closing brace was reached before any metric name was read, // e.g. for the input "{}". There is no metric to attach labels to, // so this is a malformed exposition. This mirrors the guard in - // startLabelValue. currentMF (not currentMetric) is checked because - // reset only clears currentMF between parses. + // startLabelValue. currentMetric is cleared at the start of each line. p.parseError("invalid metric name") p.currentLabelPairs = nil return nil @@ -495,7 +495,7 @@ func (p *TextParser) startLabelValue() stateFn { return p.startLabelName case '}': - if p.currentMF == nil { + if p.currentMetric == nil { p.parseError("invalid metric name") return nil } diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index e915fb98..681e6a51 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -1378,6 +1378,38 @@ func TestTextParseError(t *testing.T) { testTextParseError(t) } +func TestTextParseMissingMetricName(t *testing.T) { + for _, scheme := range []model.ValidationScheme{model.LegacyValidation, model.UTF8Validation} { + t.Run(scheme.String(), func(t *testing.T) { + for _, prefix := range []string{ + "", + "#TYPE A000000 summArY\n", + "# TYPE metric histogram\n", + "# HELP metric Help text.\n", + "metric 1\n", + "{\"metric\"} 1\n", + } { + for _, sample := range []string{"{}", "{} 2\n", "{label=\"value\"} 2\n", "{label=\"value\",} 2\n"} { + t.Run(prefix+sample, func(t *testing.T) { + p := NewTextParser(scheme) + for range 2 { + _, err := p.TextToMetricFamilies(strings.NewReader(prefix + sample)) + var parseErr ParseError + if !errors.As(err, &parseErr) || parseErr.Msg != "invalid metric name" || parseErr.Line != strings.Count(prefix, "\n")+1 { + t.Fatalf("expected invalid metric name on the sample line, got %v", err) + } + // Exercise reuse after both malformed input and a valid sample. + if _, err := p.TextToMetricFamilies(strings.NewReader("valid 1\n")); err != nil { + t.Fatalf("parsing valid sample after malformed input: %v", err) + } + } + }) + } + } + }) + } +} + func BenchmarkParseError(b *testing.B) { for i := 0; i < b.N; i++ { testTextParseError(b)