diff --git a/pgtype/hstore.go b/pgtype/hstore.go index 1a83faf55..c58173d35 100644 --- a/pgtype/hstore.go +++ b/pgtype/hstore.go @@ -431,8 +431,13 @@ func parseHstore(s string) (Hstore, error) { p := newHSP(s) // This is an over-estimate of the number of key/value pairs. Use '>' because I am guessing it - // is less likely to occur in keys/values than '=' or ','. + // is less likely to occur in keys/values than '=' or ','. Clamp so an unvalidated + // separator count cannot pre-size a huge map from garbage input. + const maxHstorePairsEstimate = 1024 numPairsEstimate := strings.Count(s, ">") + if numPairsEstimate > maxHstorePairsEstimate { + numPairsEstimate = maxHstorePairsEstimate + } // makes one allocation of strings for the entire Hstore, rather than one allocation per value. valueStrings := make([]string, 0, numPairsEstimate) result := make(Hstore, numPairsEstimate) diff --git a/pgtype/hstore_test.go b/pgtype/hstore_test.go index e56a6a655..49e3a2173 100644 --- a/pgtype/hstore_test.go +++ b/pgtype/hstore_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "strings" "testing" "time" @@ -408,3 +409,29 @@ func BenchmarkHstoreScan(b *testing.B) { }) } } + +func TestHstoreScanGarbageDoesNotPreallocateFromSeparatorCount(t *testing.T) { + var h pgtype.Hstore + err := h.Scan(strings.Repeat(">", 200000)) + if err == nil { + t.Fatal("expected error scanning invalid hstore text") + } +} + +func TestHstoreScanMorePairsThanEstimateClamp(t *testing.T) { + const n = 2000 + var b strings.Builder + for i := 0; i < n; i++ { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, `"k%d"=>"v%d"`, i, i) + } + var h pgtype.Hstore + if err := h.Scan(b.String()); err != nil { + t.Fatal(err) + } + if len(h) != n { + t.Fatalf("len(h) = %d, want %d", len(h), n) + } +}