compress/flate: clarify compatibility promise

By changing the encoder designs for each compression level in CL 707355,
compress/flate writers no longer produce byte-for-byte equivalent output
for the same input.

This is fine, we don't consider the exact byte output of a compression
algorithm to be covered by the Go 1 compatibilty promise
(https://go.dev/doc/go1compat calls out "unspecified behavior"), and we
have made these changes before, such as CL 22370 in Go 1.7.

Still, this may not be obvious, and the relatively rare changes make it
easy for golden tests depending on the output to creep into users tests.

Add an explicit note about the compatibility promise to try to nudge
users in the right direction.

This also applies to archive/zip, compress/gzip, compress/zlib, and
image/png, as they transitively use compress/flate.

Updates #75532.

Change-Id: I60e4624e5f45081d1c4696e6fb3a2b9d6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/774300
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Klaus Post <klauspost@gmail.com>
This commit is contained in:
Michael Pratt 2026-05-05 10:34:23 -04:00
parent 714a94dd31
commit 2747d887eb
5 changed files with 40 additions and 0 deletions

View file

@ -42,6 +42,10 @@ type header struct {
}
// NewWriter returns a new [Writer] writing a zip file to w.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriter(w io.Writer) *Writer {
return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}}
}

View file

@ -818,6 +818,10 @@ func (d *compressor) close() error {
//
// If level is in the range [-2, 9] then the error returned will be nil.
// Otherwise the error returned will be non-nil.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriter(w io.Writer, level int) (*Writer, error) {
var dw Writer
if err := dw.d.init(w, level); err != nil {
@ -832,6 +836,10 @@ func NewWriter(w io.Writer, level int) (*Writer, error) {
// any compressed output. The compressed data written to w
// can only be decompressed by a reader initialized with the
// same dictionary (see [NewReaderDict]).
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriterDict(w io.Writer, level int, dict []byte) (*Writer, error) {
zw, err := NewWriter(w, level)
if err != nil {

View file

@ -46,6 +46,10 @@ type Writer struct {
//
// Callers that wish to set the fields in Writer.[Header] must do so before
// the first call to Write, Flush, or Close.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriter(w io.Writer) *Writer {
z, _ := NewWriterLevel(w, DefaultCompression)
return z
@ -57,6 +61,10 @@ func NewWriter(w io.Writer) *Writer {
// The compression level can be [DefaultCompression], [NoCompression], [HuffmanOnly]
// or any integer value between [BestSpeed] and [BestCompression] inclusive.
// The error returned will be nil if the level is valid.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
if level < HuffmanOnly || level > BestCompression {
return nil, fmt.Errorf("gzip: invalid compression level: %d", level)

View file

@ -41,6 +41,10 @@ type Writer struct {
//
// It is the caller's responsibility to call Close on the Writer when done.
// Writes may be buffered and not flushed until Close.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriter(w io.Writer) *Writer {
z, _ := NewWriterLevelDict(w, DefaultCompression, nil)
return z
@ -52,6 +56,10 @@ func NewWriter(w io.Writer) *Writer {
// The compression level can be [DefaultCompression], [NoCompression], [HuffmanOnly]
// or any integer value between [BestSpeed] and [BestCompression] inclusive.
// The error returned will be nil if the level is valid.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
return NewWriterLevelDict(w, level, nil)
}
@ -61,6 +69,10 @@ func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
//
// The dictionary may be nil. If not, its contents should not be modified until
// the Writer is closed.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) {
if level < HuffmanOnly || level > BestCompression {
return nil, fmt.Errorf("zlib: invalid compression level: %d", level)

View file

@ -581,12 +581,20 @@ func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }
// Encode writes the Image m to w in PNG format. Any Image may be
// encoded, but images that are not [image.NRGBA] might be encoded lossily.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func Encode(w io.Writer, m image.Image) error {
var e Encoder
return e.Encode(w, m)
}
// Encode writes the Image m to w in PNG format.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func (enc *Encoder) Encode(w io.Writer, m image.Image) error {
// Obviously, negative widths and heights are invalid. Furthermore, the PNG
// spec section 11.2.2 says that zero is invalid. Excessively large images are