Initial commit

This commit is contained in:
eleksir
2025-05-08 18:12:52 +03:00
commit b21e3c84c0
1553 changed files with 649819 additions and 0 deletions

View File

@@ -0,0 +1,351 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package buffer
import (
"bytes"
origFmt "fmt"
"unicode/utf8"
"unsafe"
"github.com/cockroachdb/redact/internal/escape"
m "github.com/cockroachdb/redact/internal/markers"
)
// Buffer is a variable-sized buffer of bytes with a
// Write method. Writes are escaped in different ways
// depending on the output mode.
type Buffer struct {
buf []byte
validUntil int // exclusive upper bound of data that's already validated
mode OutputMode
markerOpen bool
}
// OutputMode determines how writes are processed in the Buffer.
type OutputMode int
const (
// UnsafeEscaped says that written data is unsafe for reporting,
// so it should be enclosed in redaction markers,
// and may contain redaction markers itself, and so it should be escaped.
UnsafeEscaped OutputMode = iota
// SafeEscaped says that written data is safe for reporting
// (should not be enclosed in redaction markers) but may itself
// contain redaction markers, and so it should be escaped.
SafeEscaped
// SafeRaw says that written data is safe for reporting
// (should not be enclosed in redaction markers) and is
// guaranteed not to contain redaction markers, so it
// needs not be escaped.
SafeRaw
// PreRedactable says that written data already contains a mixed
// of safe and unsafe information with suitably placed redaction markers,
// and so should be inlined.
PreRedactable = SafeRaw
)
// RedactableBytes returns the bytes in the buffer.
func (b Buffer) RedactableBytes() m.RedactableBytes {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like RedactableBytes().
b.finalize()
return m.RedactableBytes(b.buf)
}
// RedactableString returns the bytes in the buffer.
func (b Buffer) RedactableString() m.RedactableString {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like RedactableString().
b.finalize()
return m.RedactableString(b.buf)
}
// String implemens fmt.Stringer.
func (b Buffer) String() string {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like String().
b.finalize()
return m.RedactableString(b.buf).StripMarkers()
}
// TakeRedactableBytes returns the buffer
// contents and reinitializes the buffer. This saves
// a memory allocation compared to RedactableBytes().
func (b *Buffer) TakeRedactableBytes() m.RedactableBytes {
b.finalize()
r := b.buf
b.buf = nil
b.validUntil = 0
b.mode = UnsafeEscaped
return m.RedactableBytes(r)
}
// TakeRedactableString returns the buffer
// contents and reinitializes the buffer. This saves
// a memory allocation compared to RedactableString().
func (b *Buffer) TakeRedactableString() m.RedactableString {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
b.finalize()
r := *(*m.RedactableString)(unsafe.Pointer(&b.buf))
b.buf = nil
b.validUntil = 0
b.mode = UnsafeEscaped
return r
}
// Len returns the number of bytes in the buffer.
func (b *Buffer) Len() int {
copy := *b
copy.finalize()
return len(copy.buf)
}
// Cap returns the capacity of the buffer's underlying byte slice,
// that is, the total space allocated for the buffer's data.
func (b *Buffer) Cap() int {
return cap(b.buf)
}
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.startWrite()
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
b.startWrite()
m, ok := b.tryGrowByReslice(len(s))
if !ok {
m = b.grow(len(s))
}
return copy(b.buf[m:], s), nil
}
// WriteByte emits a single byte.
func (b *Buffer) WriteByte(s byte) error {
b.startWrite()
if b.mode == UnsafeEscaped &&
(s >= utf8.RuneSelf ||
s == m.StartS[0] || s == m.EndS[0]) {
// Unsafe byte. Escape it.
_, err := b.WriteString(m.EscapeMarkS)
return err
}
m, ok := b.tryGrowByReslice(1)
if !ok {
m = b.grow(1)
}
b.buf[m] = s
return nil
}
// WriteRune emits a single rune.
func (b *Buffer) WriteRune(s rune) error {
b.startWrite()
l := utf8.RuneLen(s)
m, ok := b.tryGrowByReslice(l)
if !ok {
m = b.grow(l)
}
_ = utf8.EncodeRune(b.buf[m:], s)
return nil
}
// finalize ensures that all the buffer is properly
// marked.
func (b *Buffer) finalize() {
if b.mode == SafeRaw {
b.validUntil = len(b.buf)
} else {
b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */)
}
if b.markerOpen {
b.endRedactable()
b.validUntil = len(b.buf)
}
}
// endRedactable adds the closing redaction marker.
func (b *Buffer) endRedactable() {
if len(b.buf) == 0 {
return
}
if bytes.HasSuffix(b.buf, m.StartBytes) {
// Special case: remove a trailing open marker and call it a day.
b.buf = b.buf[:len(b.buf)-m.StartLen]
} else {
p, ok := b.tryGrowByReslice(m.EndLen)
if !ok {
p = b.grow(m.EndLen)
}
copy(b.buf[p:], m.EndS)
}
b.markerOpen = false
}
func (b *Buffer) startWrite() {
if b.mode == UnsafeEscaped && !b.markerOpen {
b.startRedactable()
b.validUntil = len(b.buf)
}
}
// endRedactable adds the closing redaction marker.
func (b *Buffer) startRedactable() {
if bytes.HasSuffix(b.buf, m.EndBytes) {
// Special case: remove a trailing closing marker and call it a day.
b.buf = b.buf[:len(b.buf)-m.EndLen]
} else {
p, ok := b.tryGrowByReslice(len(m.StartS))
if !ok {
p = b.grow(len(m.StartS))
}
copy(b.buf[p:], m.StartS)
}
b.markerOpen = true
}
// escapeToEnd escapes occurrences of redaction markers in
// b.buf[b.validUntil:] and advances b.validUntil until the end.
func (b *Buffer) escapeToEnd(breakNewLines bool) {
b.buf = escape.InternalEscapeBytes(b.buf, b.validUntil, breakNewLines, false /* trim */)
b.validUntil = len(b.buf)
}
// GetMode retrieves the output mode.
func (b *Buffer) GetMode() OutputMode {
return b.mode
}
// SetMode changes the output mode.
func (b *Buffer) SetMode(newMode OutputMode) {
if b.mode == newMode {
// noop
return
}
if b.mode == UnsafeEscaped || b.mode == SafeEscaped {
b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */)
}
if b.markerOpen {
b.endRedactable()
}
b.validUntil = len(b.buf)
b.mode = newMode
}
// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// It also resets the output mode to UnsafeEscaped.
func (b *Buffer) Reset() {
b.buf = b.buf[:0]
b.validUntil = 0
b.mode = UnsafeEscaped
b.markerOpen = false
}
// tryGrowByReslice is a inlineable version of grow for the fast-case where the
// internal buffer only needs to be resliced.
// It returns the index where bytes should be written and whether it succeeded.
func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
if l := len(b.buf); n <= cap(b.buf)-l {
b.buf = b.buf[:l+n]
return l, true
}
return 0, false
}
// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {
m := len(b.buf)
// Try to grow by means of a reslice.
if i, ok := b.tryGrowByReslice(n); ok {
return i
}
if b.buf == nil && n <= smallBufferSize {
b.buf = make([]byte, n, smallBufferSize)
return 0
}
c := cap(b.buf)
const maxInt = int(^uint(0) >> 1)
if n <= c/2-m {
// let capacity get twice as large.
} else if c > maxInt-c-n {
panic(ErrTooLarge)
} else {
// Not enough space anywhere, we need to allocate.
buf := makeSlice(2*c + n)
copy(buf, b.buf)
b.buf = buf
}
b.buf = b.buf[:m+n]
return m
}
// Grow grows the buffer's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to the
// buffer without another allocation.
// If n is negative, Grow will panic.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) Grow(n int) {
if n < 0 {
panic(origFmt.Errorf("redact.Buffer.Grow: negative count"))
}
m := b.grow(n)
b.buf = b.buf[:m]
}
// clone is used in tests.
func (b *Buffer) clone() *Buffer {
c := *b
c.buf = append([]byte(nil), b.buf...)
return &c
}
// makeSlice allocates a slice of size n. If the allocation fails, it panics
// with ErrTooLarge.
func makeSlice(n int) []byte {
// If the make fails, give a known error.
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
return make([]byte, n)
}
// ErrTooLarge is passed to panic if memory cannot be allocated to
// store data in a buffer.
var ErrTooLarge = origFmt.Errorf("redact.Buffer: too large")
// smallBufferSize is an initial allocation minimal capacity.
const smallBufferSize = 64

View File

@@ -0,0 +1,137 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package escape
import (
"bytes"
"unicode/utf8"
m "github.com/cockroachdb/redact/internal/markers"
)
// InternalEscapeBytes escapes redaction markers in the provided buf
// starting at the location startLoc.
// The bytes before startLoc are considered safe (already escaped).
//
// If breakNewLines is set, a closing redaction marker
// is placed before sequences of one or more newline characters,
// and an open redaction marker is placed afterwards.
//
// If strip is set, final newlines and spaces are trimmed from the
// output.
func InternalEscapeBytes(b []byte, startLoc int, breakNewLines, strip bool) (res []byte) {
// Note: we use len(...RedactableS) and not len(...RedactableBytes)
// because the ...S variant is a compile-time constant so this
// accelerates the loops below.
start, ls := m.StartBytes, len(m.StartS)
end, le := m.EndBytes, len(m.EndS)
escape := m.EscapeMarkBytes
// Trim final newlines/spaces, for convenience.
if strip {
end := len(b)
for i := end - 1; i >= startLoc; i-- {
if b[i] == '\n' || b[i] == ' ' {
end = i
} else {
break
}
}
b = b[:end]
}
// res is the output slice. In the common case where there is
// nothing to escape, the input slice is returned directly
// and no allocation takes place.
res = b
// copied is true if and only if `res` is a copy of `b`. It only
// turns to true if the loop below finds something to escape.
copied := false
// k is the index in b up to (and excluding) the byte which we've
// already copied into res (if copied=true).
k := 0
for i := startLoc; i < len(b); i++ {
if breakNewLines && b[i] == '\n' {
if !copied {
// We only allocate an output slice when we know we definitely
// need it.
res = make([]byte, 0, len(b))
copied = true
}
res = append(res, b[k:i]...)
// Either add an end marker, or elide a start marker immediately prior.
if bytes.HasSuffix(res, start) {
res = res[:len(res)-ls]
} else {
res = append(res, end...)
}
// Advance to the last newline character. We want to forward
// them all in a single call to doWrite, for performance.
lastNewLine := i
for lastNewLine < len(b) && b[lastNewLine] == '\n' {
lastNewLine++
}
res = append(res, b[i:lastNewLine]...)
res = append(res, start...)
k = lastNewLine
i = lastNewLine - 1
} else
// Ensure that occurrences of the delimiter inside the string get
// escaped.
// Reminder: ls and le are likely greater than 1, as we are scanning
// utf-8 encoded delimiters (the utf-8 encoding is multibyte).
if i+ls <= len(b) && bytes.Equal(b[i:i+ls], start) {
if !copied {
// We only allocate an output slice when we know we definitely
// need it.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:i]...)
res = append(res, escape...)
// Advance the counters by the length (in bytes) of the delimiter.
k = i + ls
i += ls - 1 /* -1 because we have i++ at the end of every iteration */
} else if i+le <= len(b) && bytes.Equal(b[i:i+le], end) {
if !copied {
// See the comment above about res allocation.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:i]...)
res = append(res, escape...)
// Advance the counters by the length (in bytes) of the delimiter.
k = i + le
i += le - 1 /* -1 because we have i++ at the end of every iteration */
}
}
// If the string terminates with an invalid utf-8 sequence, we
// want to avoid a run-in with a subsequent redaction marker.
if r, s := utf8.DecodeLastRune(b); s == 1 && r == utf8.RuneError {
if !copied {
// See the comment above about res allocation.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:]...)
res = append(res, escape...)
k = len(b)
}
if copied {
res = append(res, b[k:]...)
}
return
}

View File

@@ -0,0 +1,88 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package fmtforward
import (
"fmt"
"io"
"strconv"
"strings"
)
// ReproducePrintf calls fmt.Print/fmt.Printf on the argument
// based on the details present in the fmt.State and
// writes to the io.Writer.
func ReproducePrintf(w io.Writer, s fmt.State, verb rune, arg interface{}) {
justV, revFmt := MakeFormat(s, verb)
if justV {
// Common case, avoids generating then parsing the format again.
fmt.Fprint(w, arg)
} else {
fmt.Fprintf(w, revFmt, arg)
}
}
// MakeFormat reproduces the format currently active
// in fmt.State and verb. This is provided because Go's standard
// fmt.State does not make the original format string available to us.
//
// If the return value justV is true, then the current state
// was found to be %v exactly; in that case the caller
// can avoid a full-blown Printf call and use just Print instead
// to take a shortcut.
func MakeFormat(s fmt.State, verb rune) (justV bool, format string) {
plus, minus, hash, sp, z := s.Flag('+'), s.Flag('-'), s.Flag('#'), s.Flag(' '), s.Flag('0')
w, wp := s.Width()
p, pp := s.Precision()
if !plus && !minus && !hash && !sp && !z && !wp && !pp {
switch verb {
case 'v':
return true, "%v"
case 's':
return false, "%s"
case 'd':
return false, "%d"
}
// Other cases handled in the slow path below.
}
var f strings.Builder
f.WriteByte('%')
if plus {
f.WriteByte('+')
}
if minus {
f.WriteByte('-')
}
if hash {
f.WriteByte('#')
}
if sp {
f.WriteByte(' ')
}
if z {
f.WriteByte('0')
}
if wp {
f.WriteString(strconv.Itoa(w))
}
if pp {
f.WriteByte('.')
f.WriteString(strconv.Itoa(p))
}
f.WriteRune(verb)
return false, f.String()
}

View File

@@ -0,0 +1,40 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package markers
import "regexp"
// Internal constants.
const (
Start = '‹'
StartS = string(Start)
StartLen = len(StartS)
End = '›'
EndS = string(End)
EndLen = len(EndS)
EscapeMark = '?'
EscapeMarkS = string(EscapeMark)
RedactedS = StartS + "×" + EndS
)
// Internal variables.
var (
StartBytes = []byte(StartS)
EndBytes = []byte(EndS)
EscapeMarkBytes = []byte(EscapeMarkS)
RedactedBytes = []byte(RedactedS)
ReStripSensitive = regexp.MustCompile(StartS + "[^" + StartS + EndS + "]*" + EndS)
ReStripMarkers = regexp.MustCompile("[" + StartS + EndS + "]")
)

View File

@@ -0,0 +1,99 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package markers
import i "github.com/cockroachdb/redact/interfaces"
// RedactableString is a string that contains a mix of safe and unsafe
// bits of data, but where it is known that unsafe bits are enclosed
// by redaction markers ‹ and ›, and occurrences of the markers
// inside the original data items have been escaped.
//
// Instances of RedactableString should not be constructed directly;
// instead use the facilities from print.go (Sprint, Sprintf)
// or the methods below.
type RedactableString string
// StripMarkers removes the redaction markers from the
// RedactableString. This returns an unsafe string where all safe and
// unsafe bits are mixed together.
func (s RedactableString) StripMarkers() string {
return ReStripMarkers.ReplaceAllString(string(s), "")
}
// Redact replaces all occurrences of unsafe substrings by the
// “Redacted” marker, ‹×›. The result string is still safe.
func (s RedactableString) Redact() RedactableString {
return RedactableString(ReStripSensitive.ReplaceAllString(string(s), RedactedS))
}
// ToBytes converts the string to a byte slice.
func (s RedactableString) ToBytes() RedactableBytes {
return RedactableBytes([]byte(string(s)))
}
// SafeFormat formats the redactable safely.
func (s RedactableString) SafeFormat(sp i.SafePrinter, _ rune) {
// As per annotateArgs() in markers_internal_print.go,
// we consider the redactable string not further formattable.
sp.Print(s)
}
// RedactableBytes is like RedactableString but is a byte slice.
//
// Instances of RedactableBytes should not be constructed directly;
// instead use the facilities from print.go (Sprint, Sprintf)
// or the methods below.
type RedactableBytes []byte
// StripMarkers removes the redaction markers from the
// RedactableBytes. This returns an unsafe string where all safe and
// unsafe bits are mixed together.
func (s RedactableBytes) StripMarkers() []byte {
return ReStripMarkers.ReplaceAll([]byte(s), nil)
}
// Redact replaces all occurrences of unsafe substrings by the
// “Redacted” marker, ‹×›.
func (s RedactableBytes) Redact() RedactableBytes {
return RedactableBytes(ReStripSensitive.ReplaceAll(s, RedactedBytes))
}
// ToString converts the byte slice to a string.
func (s RedactableBytes) ToString() RedactableString {
return RedactableString(string([]byte(s)))
}
// SafeFormat formats the redactable safely.
func (s RedactableBytes) SafeFormat(sp i.SafePrinter, _ rune) {
// As per annotateArgs() in markers_internal_print.go,
// we consider the redactable bytes not further formattable.
sp.Print(s)
}
// StartMarker returns the start delimiter for an unsafe string.
func StartMarker() []byte { return []byte(StartS) }
// EndMarker returns the end delimiter for an unsafe string.
func EndMarker() []byte { return []byte(EndS) }
// RedactedMarker returns the special string used by Redact.
func RedactedMarker() []byte { return []byte(RedactedS) }
// EscapeMarkers escapes the special delimiters from the provided
// byte slice.
func EscapeMarkers(s []byte) []byte {
return ReStripMarkers.ReplaceAll(s, EscapeMarkBytes)
}

View File

@@ -0,0 +1,19 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
// Package redact is a helper internal package that provides the
// Unsafe and Safe value wrappers. Its name is 'redact' even though a
// better name would be 'wrappers' so as to ensure that the type name
// of generated values contains the 'redact.' prefix.
package redact

View File

@@ -0,0 +1,84 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package redact
import (
origFmt "fmt"
i "github.com/cockroachdb/redact/interfaces"
"github.com/cockroachdb/redact/internal/fmtforward"
)
// Unsafe turns any value that would otherwise be considered safe,
// into an unsafe value.
func Unsafe(a interface{}) interface{} {
return unsafeWrap{a}
}
// UnsafeWrap is the type of wrapper produced by Unsafe.
// This is exported only for use by the rfmt package.
// Client packages should not make assumptions about
// the concrete return type of Unsafe().
type UnsafeWrap = unsafeWrap
type unsafeWrap struct {
a interface{}
}
func (w unsafeWrap) GetValue() interface{} { return w.a }
func (w unsafeWrap) Format(s origFmt.State, verb rune) {
fmtforward.ReproducePrintf(s, s, verb, w.a)
}
// Safe turns any value into an object that is considered as safe by
// the formatter.
//
// This is provided as an “escape hatch” for cases where the other
// interfaces and conventions fail. Increased usage of this mechanism
// should be taken as a signal that a new abstraction is missing.
// The implementation is also slow.
func Safe(a interface{}) i.SafeValue {
return safeWrapper{a}
}
// SafeWrapper is the type of wrapper produced by Safe.
// This is exported only for use by the rfmt package.
// Client packages should not make assumptions about
// the concrete return type of Safe().
type SafeWrapper = safeWrapper
type safeWrapper struct {
a interface{}
}
var _ i.SafeValue = safeWrapper{}
var _ origFmt.Formatter = safeWrapper{}
var _ i.SafeMessager = safeWrapper{}
func (w safeWrapper) GetValue() interface{} { return w.a }
// SafeValue implements the SafeValue interface.
func (w safeWrapper) SafeValue() {}
// Format implements the fmt.Formatter interface.
func (w safeWrapper) Format(s origFmt.State, verb rune) {
fmtforward.ReproducePrintf(s, s, verb, w.a)
}
// SafeMessage implements SafeMessager.
func (w safeWrapper) SafeMessage() string {
return origFmt.Sprintf("%v", w.a)
}

View File

@@ -0,0 +1,43 @@
Overall code structure
======================
This directory imports the "printf" logic as-is from the Go standard
library and instruments it for redaction of sensitive data.
Overall, the original Go code is structured as follows:
- the top-level API functions (`Printf` `Fprintf` etc) instantiate a
"printer" struct called `pp`, then call the `doPrint*()` methods
on it.
- the `pp` contains a byte slice (`pp.buf`, type `buffer`) that
accumulates the result of formatting *regardless of the final output
of the API* - i.e. a buffer is used even when using `Fprint` to an
`io.Writer`. Only after the `doPrint()` method finishes, is the
`buffer` copied to the final `io.Writer` in the `Fprint*` variants.
- each of the `doPrint` methods does some analysis on the argument
list - quite simple for e.g. `doPrintln`, more intricate for
`doPrintf`. As part of the analysis it emits "spacing" or no-op
bytes directly on the `pp.buf`. For example `doPrint` emits spaces
between arguments directly, `doPrintf` emits the non-formatting
characters from the format string, as well as certain *constant*
error strings (e.g. `%(BADPREC)`).
Refreshing the sources
======================
The files in this directory have been imported from
`$GOROOT/src/fmt/{format,print}.go`
and
`$GOROOT/src/internal/fmtsort`
And patched using the included `.diff` files.
To upgrade to a newer Go implementation, import the files anew and
re-apply the patches.
See the script `refresh.sh` for details.

View File

@@ -0,0 +1,222 @@
// Code generated from the Go standard library. DO NOT EDIT
// GENERATED FILE DO NOT EDIT
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package fmtsort provides a general stable ordering mechanism
// for maps, on behalf of the fmt and text/template packages.
// It is not guaranteed to be efficient and works only for types
// that are valid map keys.
package fmtsort
import (
"reflect"
"sort"
)
// Note: Throughout this package we avoid calling reflect.Value.Interface as
// it is not always legal to do so and it's easier to avoid the issue than to face it.
// SortedMap represents a map's keys and values. The keys and values are
// aligned in index order: Value[i] is the value in the map corresponding to Key[i].
type SortedMap struct {
Key []reflect.Value
Value []reflect.Value
}
func (o *SortedMap) Len() int { return len(o.Key) }
func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 }
func (o *SortedMap) Swap(i, j int) {
o.Key[i], o.Key[j] = o.Key[j], o.Key[i]
o.Value[i], o.Value[j] = o.Value[j], o.Value[i]
}
// Sort accepts a map and returns a SortedMap that has the same keys and
// values but in a stable sorted order according to the keys, modulo issues
// raised by unorderable key values such as NaNs.
//
// The ordering rules are more general than with Go's < operator:
//
// - when applicable, nil compares low
// - ints, floats, and strings order by <
// - NaN compares less than non-NaN floats
// - bool compares false before true
// - complex compares real, then imag
// - pointers compare by machine address
// - channel values compare by machine address
// - structs compare each field in turn
// - arrays compare each element in turn.
// Otherwise identical arrays compare by length.
// - interface values compare first by reflect.Type describing the concrete type
// and then by concrete value as described in the previous rules.
//
func Sort(mapValue reflect.Value) *SortedMap {
if mapValue.Type().Kind() != reflect.Map {
return nil
}
// Note: this code is arranged to not panic even in the presence
// of a concurrent map update. The runtime is responsible for
// yelling loudly if that happens. See issue 33275.
n := mapValue.Len()
key := make([]reflect.Value, 0, n)
value := make([]reflect.Value, 0, n)
iter := mapValue.MapRange()
for iter.Next() {
key = append(key, iter.Key())
value = append(value, iter.Value())
}
sorted := &SortedMap{
Key: key,
Value: value,
}
sort.Stable(sorted)
return sorted
}
// compare compares two values of the same type. It returns -1, 0, 1
// according to whether a > b (1), a == b (0), or a < b (-1).
// If the types differ, it returns -1.
// See the comment on Sort for the comparison rules.
func compare(aVal, bVal reflect.Value) int {
aType, bType := aVal.Type(), bVal.Type()
if aType != bType {
return -1 // No good answer possible, but don't return 0: they're not equal.
}
switch aVal.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
a, b := aVal.Int(), bVal.Int()
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
a, b := aVal.Uint(), bVal.Uint()
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
case reflect.String:
a, b := aVal.String(), bVal.String()
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
case reflect.Float32, reflect.Float64:
return floatCompare(aVal.Float(), bVal.Float())
case reflect.Complex64, reflect.Complex128:
a, b := aVal.Complex(), bVal.Complex()
if c := floatCompare(real(a), real(b)); c != 0 {
return c
}
return floatCompare(imag(a), imag(b))
case reflect.Bool:
a, b := aVal.Bool(), bVal.Bool()
switch {
case a == b:
return 0
case a:
return 1
default:
return -1
}
case reflect.Ptr, reflect.UnsafePointer:
a, b := aVal.Pointer(), bVal.Pointer()
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
case reflect.Chan:
if c, ok := nilCompare(aVal, bVal); ok {
return c
}
ap, bp := aVal.Pointer(), bVal.Pointer()
switch {
case ap < bp:
return -1
case ap > bp:
return 1
default:
return 0
}
case reflect.Struct:
for i := 0; i < aVal.NumField(); i++ {
if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 {
return c
}
}
return 0
case reflect.Array:
for i := 0; i < aVal.Len(); i++ {
if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 {
return c
}
}
return 0
case reflect.Interface:
if c, ok := nilCompare(aVal, bVal); ok {
return c
}
c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type()))
if c != 0 {
return c
}
return compare(aVal.Elem(), bVal.Elem())
default:
// Certain types cannot appear as keys (maps, funcs, slices), but be explicit.
panic("bad type in compare: " + aType.String())
}
}
// nilCompare checks whether either value is nil. If not, the boolean is false.
// If either value is nil, the boolean is true and the integer is the comparison
// value. The comparison is defined to be 0 if both are nil, otherwise the one
// nil value compares low. Both arguments must represent a chan, func,
// interface, map, pointer, or slice.
func nilCompare(aVal, bVal reflect.Value) (int, bool) {
if aVal.IsNil() {
if bVal.IsNil() {
return 0, true
}
return -1, true
}
if bVal.IsNil() {
return 1, true
}
return 0, false
}
// floatCompare compares two floating-point values. NaNs compare low.
func floatCompare(a, b float64) int {
switch {
case isNaN(a):
return -1 // No good answer if b is a NaN so don't bother checking.
case isNaN(b):
return 1
case a < b:
return -1
case a > b:
return 1
}
return 0
}
func isNaN(a float64) bool {
return a != a
}

View File

@@ -0,0 +1,589 @@
// Code generated from the Go standard library. DO NOT EDIT
// GENERATED FILE DO NOT EDIT
//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rfmt
import (
"strconv"
"unicode/utf8"
)
const (
ldigits = "0123456789abcdefx"
udigits = "0123456789ABCDEFX"
)
const (
signed = true
unsigned = false
)
// flags placed in a separate struct for easy clearing.
type fmtFlags struct {
widPresent bool
precPresent bool
minus bool
plus bool
sharp bool
space bool
zero bool
// For the formats %+v %#v, we set the plusV/sharpV flags
// and clear the plus/sharp flags since %+v and %#v are in effect
// different, flagless formats set at the top level.
plusV bool
sharpV bool
}
// A fmt is the raw formatter used by Printf etc.
// It prints into a buffer that must be set up separately.
type fmt struct {
buf *buffer
fmtFlags
wid int // width
prec int // precision
// intbuf is large enough to store %b of an int64 with a sign and
// avoids padding at the end of the struct on 32 bit architectures.
intbuf [68]byte
}
func (f *fmt) clearflags() {
f.fmtFlags = fmtFlags{}
}
func (f *fmt) init(buf *buffer) {
f.buf = buf
f.clearflags()
}
// writePadding generates n bytes of padding.
func (f *fmt) writePadding(n int) {
if n <= 0 { // No padding bytes needed.
return
}
// Make enough room for padding.
f.buf.Grow(n)
// Decide which byte the padding should be filled with.
padByte := byte(' ')
if f.zero {
padByte = byte('0')
}
// Fill padding with padByte.
for i := 0; i < n; i++ {
f.buf.writeByte(padByte)
}
}
// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
func (f *fmt) pad(b []byte) {
if !f.widPresent || f.wid == 0 {
f.buf.write(b)
return
}
width := f.wid - utf8.RuneCount(b)
if !f.minus {
// left padding
f.writePadding(width)
f.buf.write(b)
} else {
// right padding
f.buf.write(b)
f.writePadding(width)
}
}
// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus).
func (f *fmt) padString(s string) {
if !f.widPresent || f.wid == 0 {
f.buf.writeString(s)
return
}
width := f.wid - utf8.RuneCountInString(s)
if !f.minus {
// left padding
f.writePadding(width)
f.buf.writeString(s)
} else {
// right padding
f.buf.writeString(s)
f.writePadding(width)
}
}
// fmtBoolean formats a boolean.
func (f *fmt) fmtBoolean(v bool) {
if v {
f.padString("true")
} else {
f.padString("false")
}
}
// fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'".
func (f *fmt) fmtUnicode(u uint64) {
buf := f.intbuf[0:]
// With default precision set the maximum needed buf length is 18
// for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits
// into the already allocated intbuf with a capacity of 68 bytes.
prec := 4
if f.precPresent && f.prec > 4 {
prec = f.prec
// Compute space needed for "U+" , number, " '", character, "'".
width := 2 + prec + 2 + utf8.UTFMax + 1
if width > len(buf) {
buf = make([]byte, width)
}
}
// Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left.
i := len(buf)
// For %#U we want to add a space and a quoted character at the end of the buffer.
if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {
i--
buf[i] = '\''
i -= utf8.RuneLen(rune(u))
utf8.EncodeRune(buf[i:], rune(u))
i--
buf[i] = '\''
i--
buf[i] = ' '
}
// Format the Unicode code point u as a hexadecimal number.
for u >= 16 {
i--
buf[i] = udigits[u&0xF]
prec--
u >>= 4
}
i--
buf[i] = udigits[u]
prec--
// Add zeros in front of the number until requested precision is reached.
for prec > 0 {
i--
buf[i] = '0'
prec--
}
// Add a leading "U+".
i--
buf[i] = '+'
i--
buf[i] = 'U'
oldZero := f.zero
f.zero = false
f.pad(buf[i:])
f.zero = oldZero
}
// fmtInteger formats signed and unsigned integers.
func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) {
negative := isSigned && int64(u) < 0
if negative {
u = -u
}
buf := f.intbuf[0:]
// The already allocated f.intbuf with a capacity of 68 bytes
// is large enough for integer formatting when no precision or width is set.
if f.widPresent || f.precPresent {
// Account 3 extra bytes for possible addition of a sign and "0x".
width := 3 + f.wid + f.prec // wid and prec are always positive.
if width > len(buf) {
// We're going to need a bigger boat.
buf = make([]byte, width)
}
}
// Two ways to ask for extra leading zero digits: %.3d or %03d.
// If both are specified the f.zero flag is ignored and
// padding with spaces is used instead.
prec := 0
if f.precPresent {
prec = f.prec
// Precision of 0 and value of 0 means "print nothing" but padding.
if prec == 0 && u == 0 {
oldZero := f.zero
f.zero = false
f.writePadding(f.wid)
f.zero = oldZero
return
}
} else if f.zero && f.widPresent {
prec = f.wid
if negative || f.plus || f.space {
prec-- // leave room for sign
}
}
// Because printing is easier right-to-left: format u into buf, ending at buf[i].
// We could make things marginally faster by splitting the 32-bit case out
// into a separate block but it's not worth the duplication, so u has 64 bits.
i := len(buf)
// Use constants for the division and modulo for more efficient code.
// Switch cases ordered by popularity.
switch base {
case 10:
for u >= 10 {
i--
next := u / 10
buf[i] = byte('0' + u - next*10)
u = next
}
case 16:
for u >= 16 {
i--
buf[i] = digits[u&0xF]
u >>= 4
}
case 8:
for u >= 8 {
i--
buf[i] = byte('0' + u&7)
u >>= 3
}
case 2:
for u >= 2 {
i--
buf[i] = byte('0' + u&1)
u >>= 1
}
default:
panic("fmt: unknown base; can't happen")
}
i--
buf[i] = digits[u]
for i > 0 && prec > len(buf)-i {
i--
buf[i] = '0'
}
// Various prefixes: 0x, -, etc.
if f.sharp {
switch base {
case 2:
// Add a leading 0b.
i--
buf[i] = 'b'
i--
buf[i] = '0'
case 8:
if buf[i] != '0' {
i--
buf[i] = '0'
}
case 16:
// Add a leading 0x or 0X.
i--
buf[i] = digits[16]
i--
buf[i] = '0'
}
}
if verb == 'O' {
i--
buf[i] = 'o'
i--
buf[i] = '0'
}
if negative {
i--
buf[i] = '-'
} else if f.plus {
i--
buf[i] = '+'
} else if f.space {
i--
buf[i] = ' '
}
// Left padding with zeros has already been handled like precision earlier
// or the f.zero flag is ignored due to an explicitly set precision.
oldZero := f.zero
f.zero = false
f.pad(buf[i:])
f.zero = oldZero
}
// truncateString truncates the string s to the specified precision, if present.
func (f *fmt) truncateString(s string) string {
if f.precPresent {
n := f.prec
for i := range s {
n--
if n < 0 {
return s[:i]
}
}
}
return s
}
// truncate truncates the byte slice b as a string of the specified precision, if present.
func (f *fmt) truncate(b []byte) []byte {
if f.precPresent {
n := f.prec
for i := 0; i < len(b); {
n--
if n < 0 {
return b[:i]
}
wid := 1
if b[i] >= utf8.RuneSelf {
_, wid = utf8.DecodeRune(b[i:])
}
i += wid
}
}
return b
}
// fmtS formats a string.
func (f *fmt) fmtS(s string) {
s = f.truncateString(s)
f.padString(s)
}
// fmtBs formats the byte slice b as if it was formatted as string with fmtS.
func (f *fmt) fmtBs(b []byte) {
b = f.truncate(b)
f.pad(b)
}
// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes.
func (f *fmt) fmtSbx(s string, b []byte, digits string) {
length := len(b)
if b == nil {
// No byte slice present. Assume string s should be encoded.
length = len(s)
}
// Set length to not process more bytes than the precision demands.
if f.precPresent && f.prec < length {
length = f.prec
}
// Compute width of the encoding taking into account the f.sharp and f.space flag.
width := 2 * length
if width > 0 {
if f.space {
// Each element encoded by two hexadecimals will get a leading 0x or 0X.
if f.sharp {
width *= 2
}
// Elements will be separated by a space.
width += length - 1
} else if f.sharp {
// Only a leading 0x or 0X will be added for the whole string.
width += 2
}
} else { // The byte slice or string that should be encoded is empty.
if f.widPresent {
f.writePadding(f.wid)
}
return
}
// Handle padding to the left.
if f.widPresent && f.wid > width && !f.minus {
f.writePadding(f.wid - width)
}
// Write the encoding directly into the output buffer.
if f.sharp {
// Add leading 0x or 0X.
f.buf.WriteByte('0')
f.buf.WriteByte(digits[16])
}
var c byte
for i := 0; i < length; i++ {
if f.space && i > 0 {
// Separate elements with a space.
f.buf.WriteByte(' ')
if f.sharp {
// Add leading 0x or 0X for each element.
f.buf.WriteByte('0')
f.buf.WriteByte(digits[16])
}
}
if b != nil {
c = b[i] // Take a byte from the input byte slice.
} else {
c = s[i] // Take a byte from the input string.
}
// Encode each byte as two hexadecimal digits.
f.buf.WriteByte(digits[c>>4])
f.buf.WriteByte(digits[c&0xF])
}
// Handle padding to the right.
if f.widPresent && f.wid > width && f.minus {
f.writePadding(f.wid - width)
}
}
// fmtSx formats a string as a hexadecimal encoding of its bytes.
func (f *fmt) fmtSx(s, digits string) {
f.fmtSbx(s, nil, digits)
}
// fmtBx formats a byte slice as a hexadecimal encoding of its bytes.
func (f *fmt) fmtBx(b []byte, digits string) {
f.fmtSbx("", b, digits)
}
// fmtQ formats a string as a double-quoted, escaped Go string constant.
// If f.sharp is set a raw (backquoted) string may be returned instead
// if the string does not contain any control characters other than tab.
func (f *fmt) fmtQ(s string) {
s = f.truncateString(s)
if f.sharp && strconv.CanBackquote(s) {
f.padString("`" + s + "`")
return
}
buf := f.intbuf[:0]
if f.plus {
f.pad(strconv.AppendQuoteToASCII(buf, s))
} else {
f.pad(strconv.AppendQuote(buf, s))
}
}
// fmtC formats an integer as a Unicode character.
// If the character is not valid Unicode, it will print '\ufffd'.
func (f *fmt) fmtC(c uint64) {
r := rune(c)
if c > utf8.MaxRune {
r = utf8.RuneError
}
buf := f.intbuf[:0]
w := utf8.EncodeRune(buf[:utf8.UTFMax], r)
f.pad(buf[:w])
}
// fmtQc formats an integer as a single-quoted, escaped Go character constant.
// If the character is not valid Unicode, it will print '\ufffd'.
func (f *fmt) fmtQc(c uint64) {
r := rune(c)
if c > utf8.MaxRune {
r = utf8.RuneError
}
buf := f.intbuf[:0]
if f.plus {
f.pad(strconv.AppendQuoteRuneToASCII(buf, r))
} else {
f.pad(strconv.AppendQuoteRune(buf, r))
}
}
// fmtFloat formats a float64. It assumes that verb is a valid format specifier
// for strconv.AppendFloat and therefore fits into a byte.
func (f *fmt) fmtFloat(v float64, size int, verb rune, prec int) {
// Explicit precision in format specifier overrules default precision.
if f.precPresent {
prec = f.prec
}
// Format number, reserving space for leading + sign if needed.
num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)
if num[1] == '-' || num[1] == '+' {
num = num[1:]
} else {
num[0] = '+'
}
// f.space means to add a leading space instead of a "+" sign unless
// the sign is explicitly asked for by f.plus.
if f.space && num[0] == '+' && !f.plus {
num[0] = ' '
}
// Special handling for infinities and NaN,
// which don't look like a number so shouldn't be padded with zeros.
if num[1] == 'I' || num[1] == 'N' {
oldZero := f.zero
f.zero = false
// Remove sign before NaN if not asked for.
if num[1] == 'N' && !f.space && !f.plus {
num = num[1:]
}
f.pad(num)
f.zero = oldZero
return
}
// The sharp flag forces printing a decimal point for non-binary formats
// and retains trailing zeros, which we may need to restore.
if f.sharp && verb != 'b' {
digits := 0
switch verb {
case 'v', 'g', 'G', 'x':
digits = prec
// If no precision is set explicitly use a precision of 6.
if digits == -1 {
digits = 6
}
}
// Buffer pre-allocated with enough room for
// exponent notations of the form "e+123" or "p-1023".
var tailBuf [6]byte
tail := tailBuf[:0]
hasDecimalPoint := false
sawNonzeroDigit := false
// Starting from i = 1 to skip sign at num[0].
for i := 1; i < len(num); i++ {
switch num[i] {
case '.':
hasDecimalPoint = true
case 'p', 'P':
tail = append(tail, num[i:]...)
num = num[:i]
case 'e', 'E':
if verb != 'x' && verb != 'X' {
tail = append(tail, num[i:]...)
num = num[:i]
break
}
fallthrough
default:
if num[i] != '0' {
sawNonzeroDigit = true
}
// Count significant digits after the first non-zero digit.
if sawNonzeroDigit {
digits--
}
}
}
if !hasDecimalPoint {
// Leading digit 0 should contribute once to digits.
if len(num) == 2 && num[1] == '0' {
digits--
}
num = append(num, '.')
}
for digits > 0 {
num = append(num, '0')
digits--
}
num = append(num, tail...)
}
// We want a sign if asked for and if the sign is not positive.
if f.plus || num[0] != '+' {
// If we're zero padding to the left we want the sign before the leading zeros.
// Achieve this by writing the sign out and then padding the unsigned number.
if f.zero && f.widPresent && f.wid > len(num) {
f.buf.writeByte(num[0])
f.writePadding(f.wid - len(num))
f.buf.write(num[1:])
return
}
f.pad(num)
return
}
// No sign to show and the number is positive; just print the unsigned number.
f.pad(num[1:])
}

View File

@@ -0,0 +1,81 @@
--- format.go.orig 2022-01-07 15:25:57.762807000 +0100
+++ format.go 2022-01-07 15:26:16.154491000 +0100
@@ -1,8 +1,11 @@
+// Code generated from the Go standard library. DO NOT EDIT
+// GENERATED FILE DO NOT EDIT
+//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package fmt
+package rfmt
import (
"strconv"
@@ -65,25 +68,17 @@
if n <= 0 { // No padding bytes needed.
return
}
- buf := *f.buf
- oldLen := len(buf)
- newLen := oldLen + n
// Make enough room for padding.
- if newLen > cap(buf) {
- buf = make(buffer, cap(buf)*2+n)
- copy(buf, *f.buf)
- }
+ f.buf.Grow(n)
// Decide which byte the padding should be filled with.
padByte := byte(' ')
if f.zero {
padByte = byte('0')
}
// Fill padding with padByte.
- padding := buf[oldLen:newLen]
- for i := range padding {
- padding[i] = padByte
+ for i := 0; i < n; i++ {
+ f.buf.writeByte(padByte)
}
- *f.buf = buf[:newLen]
}
// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
@@ -401,19 +396,20 @@
f.writePadding(f.wid - width)
}
// Write the encoding directly into the output buffer.
- buf := *f.buf
if f.sharp {
// Add leading 0x or 0X.
- buf = append(buf, '0', digits[16])
+ f.buf.WriteByte('0')
+ f.buf.WriteByte(digits[16])
}
var c byte
for i := 0; i < length; i++ {
if f.space && i > 0 {
// Separate elements with a space.
- buf = append(buf, ' ')
+ f.buf.WriteByte(' ')
if f.sharp {
// Add leading 0x or 0X for each element.
- buf = append(buf, '0', digits[16])
+ f.buf.WriteByte('0')
+ f.buf.WriteByte(digits[16])
}
}
if b != nil {
@@ -422,9 +418,9 @@
c = s[i] // Take a byte from the input string.
}
// Encode each byte as two hexadecimal digits.
- buf = append(buf, digits[c>>4], digits[c&0xF])
+ f.buf.WriteByte(digits[c>>4])
+ f.buf.WriteByte(digits[c&0xF])
}
- *f.buf = buf
// Handle padding to the right.
if f.widPresent && f.wid > width && f.minus {
f.writePadding(f.wid - width)

View File

@@ -0,0 +1,160 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package rfmt
import (
"reflect"
i "github.com/cockroachdb/redact/interfaces"
b "github.com/cockroachdb/redact/internal/buffer"
"github.com/cockroachdb/redact/internal/escape"
m "github.com/cockroachdb/redact/internal/markers"
rwrap "github.com/cockroachdb/redact/internal/redact"
)
type overrideMode int
const (
noOverride overrideMode = iota
// Consider unsafe values as safe.
overrideSafe
// Consider safe and pre-redactable values as unsafe.
overrideUnsafe
)
func (p *pp) startUnsafe() restorer {
prevMode := p.buf.GetMode()
if p.override != overrideSafe {
p.buf.SetMode(b.UnsafeEscaped)
}
return restorer{p, prevMode, p.override}
}
func (p *pp) startPreRedactable() restorer {
prevMode := p.buf.GetMode()
if p.override != overrideUnsafe {
p.buf.SetMode(b.PreRedactable)
}
return restorer{p, prevMode, p.override}
}
func (p *pp) startSafeOverride() restorer {
prevMode := p.buf.GetMode()
prevOverride := p.override
if p.override == noOverride {
p.buf.SetMode(b.SafeEscaped)
p.override = overrideSafe
}
return restorer{p, prevMode, prevOverride}
}
func (p *pp) startUnsafeOverride() restorer {
prevMode := p.buf.GetMode()
prevOverride := p.override
if p.override == noOverride {
p.buf.SetMode(b.UnsafeEscaped)
p.override = overrideUnsafe
}
return restorer{p, prevMode, prevOverride}
}
type restorer struct {
p *pp
prevMode b.OutputMode
prevOverride overrideMode
}
func (r restorer) restore() {
r.p.buf.SetMode(r.prevMode)
r.p.override = r.prevOverride
}
func (p *pp) handleSpecialValues(
value reflect.Value, t reflect.Type, verb rune, depth int,
) (handled bool) {
switch t {
case safeWrapperType:
handled = true
defer p.startSafeOverride().restore()
p.printValue(value.Field(0), verb, depth+1)
case unsafeWrapperType:
handled = true
defer p.startUnsafeOverride().restore()
p.printValue(value.Field(0), verb, depth+1)
case redactableStringType:
handled = true
defer p.startPreRedactable().restore()
p.buf.WriteString(value.String())
case redactableBytesType:
handled = true
defer p.startPreRedactable().restore()
p.buf.Write(value.Bytes())
}
return handled
}
// Sprintfn produces a RedactableString using the provided
// SafeFormat-alike function.
func Sprintfn(printer func(w i.SafePrinter)) m.RedactableString {
p := newPrinter()
printer(p)
s := p.buf.TakeRedactableString()
p.free()
return s
}
// HelperForErrorf is a helper to implement a redaction-aware
// fmt.Errorf-compatible function in a different package. It formats
// the string according to the given format and arguments in the same
// way as Sprintf, but in addition to this if the format contains %w
// and an error object in the proper argument position it also returns
// that error object.
//
// Note: This function only works if an error redaction function
// has been injected with RegisterRedactErrorFn().
func HelperForErrorf(format string, args ...interface{}) (m.RedactableString, error) {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, args)
e := p.wrappedErr
s := p.buf.TakeRedactableString()
p.free()
return s, e
}
// EscapeBytes escapes markers inside the given byte slice and encloses
// the entire byte slice between redaction markers.
// EscapeBytes escapes markers inside the given byte slice and encloses
// the entire byte slice between redaction markers.
func EscapeBytes(s []byte) m.RedactableBytes {
buf := make([]byte, 0, len(s)+len(m.StartS)+len(m.EndS))
buf = append(buf, m.StartS...)
start := len(buf)
buf = append(buf, s...)
buf = escape.InternalEscapeBytes(buf, start, true /* breakNewLine */, false /* strip */)
buf = append(buf, m.EndS...)
return m.RedactableBytes(buf)
}
var (
unsafeWrapperType = reflect.TypeOf(rwrap.UnsafeWrap{})
safeWrapperType = reflect.TypeOf(rwrap.SafeWrapper{})
redactableStringType = reflect.TypeOf(m.RedactableString(""))
redactableBytesType = reflect.TypeOf(m.RedactableBytes{})
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,489 @@
--- print.go.orig 2021-06-18 16:45:56.623967000 +0200
+++ print.go 2021-06-19 16:26:32.751061000 +0200
@@ -1,16 +1,27 @@
+// Code generated from print.go.orig. DO NOT EDIT
+// GENERATED FILE DO NOT EDIT
+//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package fmt
+package rfmt
import (
- "internal/fmtsort"
+ // CUSTOM: needed to avoid a type mismatch on Formatter.
+ origFmt "fmt"
"io"
"os"
"reflect"
"sync"
"unicode/utf8"
+
+ // CUSTOM: our own imports.
+ i "github.com/cockroachdb/redact/interfaces"
+ b "github.com/cockroachdb/redact/internal/buffer"
+ m "github.com/cockroachdb/redact/internal/markers"
+ w "github.com/cockroachdb/redact/internal/redact"
+ "github.com/cockroachdb/redact/internal/rfmt/fmtsort"
)
// Strings for use with buffer.WriteString.
@@ -51,7 +62,8 @@
// The implementation controls how State and rune are interpreted,
// and may call Sprint(f) or Fprint(f) etc. to generate its output.
type Formatter interface {
- Format(f State, verb rune)
+ // CUSTOM: refer to the original type, not the one defined here.
+ Format(f origFmt.State, verb rune)
}
// Stringer is implemented by any value that has a String method,
@@ -71,40 +83,34 @@
GoString() string
}
-// Use simple []byte instead of bytes.Buffer to avoid large dependency.
-type buffer []byte
+// CUSTOM: smarter buffer.
+type buffer struct {
+ b.Buffer
+}
func (b *buffer) write(p []byte) {
- *b = append(*b, p...)
+ b.Write(p)
}
func (b *buffer) writeString(s string) {
- *b = append(*b, s...)
+ b.WriteString(s)
}
func (b *buffer) writeByte(c byte) {
- *b = append(*b, c)
+ b.WriteByte(c)
}
func (bp *buffer) writeRune(r rune) {
- if r < utf8.RuneSelf {
- *bp = append(*bp, byte(r))
- return
- }
-
- b := *bp
- n := len(b)
- for n+utf8.UTFMax > cap(b) {
- b = append(b, 0)
- }
- w := utf8.EncodeRune(b[n:n+utf8.UTFMax], r)
- *bp = b[:n+w]
+ bp.WriteRune(r)
}
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
buf buffer
+ // CUSTOM: override safety in recursive calls.
+ override overrideMode
+
// arg holds the current item, as an interface{}.
arg interface{}
@@ -150,11 +156,11 @@
// to place back in the pool.
//
// See https://golang.org/issue/23199
- if cap(p.buf) > 64<<10 {
+ if p.buf.Cap() > 64<<10 {
return
}
- p.buf = p.buf[:0]
+ p.buf.Reset()
p.arg = nil
p.value = reflect.Value{}
p.wrappedErr = nil
@@ -183,14 +189,16 @@
// Implement Write so we can call Fprintf on a pp (through State), for
// recursive use in custom verbs.
-func (p *pp) Write(b []byte) (ret int, err error) {
- p.buf.write(b)
- return len(b), nil
+func (p *pp) Write(bs []byte) (ret int, err error) {
+ defer p.startUnsafe().restore()
+ p.buf.write(bs)
+ return len(bs), nil
}
// Implement WriteString so that we can call io.WriteString
// on a pp (through state), for efficiency.
func (p *pp) WriteString(s string) (ret int, err error) {
+ defer p.startUnsafe().restore()
p.buf.writeString(s)
return len(s), nil
}
@@ -202,7 +210,7 @@
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
- n, err = w.Write(p.buf)
+ n, err = w.Write([]byte(p.buf.TakeRedactableBytes()))
p.free()
return
}
@@ -214,10 +222,10 @@
}
// Sprintf formats according to a format specifier and returns the resulting string.
-func Sprintf(format string, a ...interface{}) string {
+func Sprintf(format string, a ...interface{}) m.RedactableString {
p := newPrinter()
p.doPrintf(format, a)
- s := string(p.buf)
+ s := p.buf.TakeRedactableString()
p.free()
return s
}
@@ -230,7 +238,7 @@
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrint(a)
- n, err = w.Write(p.buf)
+ n, err = w.Write([]byte(p.buf.TakeRedactableBytes()))
p.free()
return
}
@@ -244,10 +252,10 @@
// Sprint formats using the default formats for its operands and returns the resulting string.
// Spaces are added between operands when neither is a string.
-func Sprint(a ...interface{}) string {
+func Sprint(a ...interface{}) m.RedactableString {
p := newPrinter()
p.doPrint(a)
- s := string(p.buf)
+ s := p.buf.TakeRedactableString()
p.free()
return s
}
@@ -262,7 +270,7 @@
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintln(a)
- n, err = w.Write(p.buf)
+ n, err = w.Write([]byte(p.buf.TakeRedactableBytes()))
p.free()
return
}
@@ -276,10 +284,10 @@
// Sprintln formats using the default formats for its operands and returns the resulting string.
// Spaces are always added between operands and a newline is appended.
-func Sprintln(a ...interface{}) string {
+func Sprintln(a ...interface{}) m.RedactableString {
p := newPrinter()
p.doPrintln(a)
- s := string(p.buf)
+ s := p.buf.TakeRedactableString()
p.free()
return s
}
@@ -351,6 +359,7 @@
func (p *pp) fmtBool(v bool, verb rune) {
switch verb {
case 't', 'v':
+ defer p.startUnsafe().restore()
p.fmt.fmtBoolean(v)
default:
p.badVerb(verb)
@@ -362,6 +371,7 @@
func (p *pp) fmt0x64(v uint64, leading0x bool) {
sharp := p.fmt.sharp
p.fmt.sharp = leading0x
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits)
p.fmt.sharp = sharp
}
@@ -373,23 +383,32 @@
if p.fmt.sharpV && !isSigned {
p.fmt0x64(v, true)
} else {
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits)
}
case 'd':
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits)
case 'b':
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits)
case 'o', 'O':
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits)
case 'x':
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits)
case 'X':
+ defer p.startUnsafe().restore()
p.fmt.fmtInteger(v, 16, isSigned, verb, udigits)
case 'c':
+ defer p.startUnsafe().restore()
p.fmt.fmtC(v)
case 'q':
+ defer p.startUnsafe().restore()
p.fmt.fmtQc(v)
case 'U':
+ defer p.startUnsafe().restore()
p.fmt.fmtUnicode(v)
default:
p.badVerb(verb)
@@ -401,12 +420,16 @@
func (p *pp) fmtFloat(v float64, size int, verb rune) {
switch verb {
case 'v':
+ defer p.startUnsafe().restore()
p.fmt.fmtFloat(v, size, 'g', -1)
case 'b', 'g', 'G', 'x', 'X':
+ defer p.startUnsafe().restore()
p.fmt.fmtFloat(v, size, verb, -1)
case 'f', 'e', 'E':
+ defer p.startUnsafe().restore()
p.fmt.fmtFloat(v, size, verb, 6)
case 'F':
+ defer p.startUnsafe().restore()
p.fmt.fmtFloat(v, size, 'f', 6)
default:
p.badVerb(verb)
@@ -437,18 +460,23 @@
func (p *pp) fmtString(v string, verb rune) {
switch verb {
case 'v':
+ defer p.startUnsafe().restore()
if p.fmt.sharpV {
p.fmt.fmtQ(v)
} else {
p.fmt.fmtS(v)
}
case 's':
+ defer p.startUnsafe().restore()
p.fmt.fmtS(v)
case 'x':
+ defer p.startUnsafe().restore()
p.fmt.fmtSx(v, ldigits)
case 'X':
+ defer p.startUnsafe().restore()
p.fmt.fmtSx(v, udigits)
case 'q':
+ defer p.startUnsafe().restore()
p.fmt.fmtQ(v)
default:
p.badVerb(verb)
@@ -478,17 +506,24 @@
if i > 0 {
p.buf.writeByte(' ')
}
- p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits)
+ func(p *pp, c byte, verb rune) {
+ defer p.startUnsafe().restore()
+ p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits)
+ }(p, c, verb)
}
p.buf.writeByte(']')
}
case 's':
+ defer p.startUnsafe().restore()
p.fmt.fmtBs(v)
case 'x':
+ defer p.startUnsafe().restore()
p.fmt.fmtBx(v, ldigits)
case 'X':
+ defer p.startUnsafe().restore()
p.fmt.fmtBx(v, udigits)
case 'q':
+ defer p.startUnsafe().restore()
p.fmt.fmtQ(string(v))
default:
p.printValue(reflect.ValueOf(v), verb, 0)
@@ -519,6 +554,7 @@
p.buf.writeByte(')')
} else {
if u == 0 {
+ defer p.startUnsafe().restore()
p.fmt.padString(nilAngleString)
} else {
p.fmt0x64(uint64(u), !p.fmt.sharp)
@@ -586,10 +622,37 @@
verb = 'v'
}
+ if p.override != overrideUnsafe {
+ switch v := p.arg.(type) {
+ case i.SafeFormatter:
+ handled = true
+ defer p.catchPanic(p.arg, verb, "SafeFormat")
+ v.SafeFormat(p, verb)
+ return
+
+ case i.SafeMessager:
+ handled = true
+ defer p.catchPanic(p.arg, verb, "SafeMessager")
+ defer p.startSafeOverride().restore()
+ p.fmtString(v.SafeMessage(), verb)
+ return
+
+ case error:
+ if redactErrorFn != nil {
+ handled = true
+ defer p.catchPanic(p.arg, verb, "SafeFormatter")
+ redactErrorFn(v, p, verb)
+ return
+ }
+ }
+ }
+
// Is it a Formatter?
if formatter, ok := p.arg.(Formatter); ok {
handled = true
defer p.catchPanic(p.arg, verb, "Format")
+ // The mode is implicit: the Format() call will
+ // use p.Write() which already sets the mode.
formatter.Format(p, verb)
return
}
@@ -600,6 +663,7 @@
handled = true
defer p.catchPanic(p.arg, verb, "GoString")
// Print the result of GoString unadorned.
+ defer p.startUnsafe().restore()
p.fmt.fmtS(stringer.GoString())
return
}
@@ -632,6 +696,21 @@
}
func (p *pp) printArg(arg interface{}, verb rune) {
+ t := reflect.TypeOf(arg)
+ if safeTypeRegistry[t] {
+ defer p.startSafeOverride().restore()
+ } else if t == safeWrapperType {
+ defer p.startSafeOverride().restore()
+ arg = arg.(w.SafeWrapper).GetValue()
+ } else if t == unsafeWrapperType {
+ defer p.startUnsafeOverride().restore()
+ arg = arg.(w.UnsafeWrap).GetValue()
+ }
+
+ if _, ok := arg.(i.SafeValue); ok {
+ defer p.startSafeOverride().restore()
+ }
+
p.arg = arg
p.value = reflect.Value{}
@@ -697,13 +776,35 @@
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
- if f.IsValid() && f.CanInterface() {
- p.arg = f.Interface()
- if p.handleMethods(verb) {
+ if f.IsValid() {
+ t := f.Type()
+ if p.handleSpecialValues(f, t, verb, 0) {
return
}
+
+ if safeTypeRegistry[t] {
+ defer p.startSafeOverride().restore()
+ }
+
+ if f.CanInterface() {
+ p.arg = f.Interface()
+ if _, ok := p.arg.(i.SafeValue); ok {
+ defer p.startSafeOverride().restore()
+ }
+ if p.handleMethods(verb) {
+ return
+ }
+ }
}
p.printValue(f, verb, 0)
+ case m.RedactableString:
+ defer p.startPreRedactable().restore()
+ p.buf.WriteString(string(f))
+ return
+ case m.RedactableBytes:
+ defer p.startPreRedactable().restore()
+ p.buf.Write([]byte(f))
+ return
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
@@ -718,11 +819,25 @@
// It does not handle 'p' and 'T' verbs because these should have been already handled by printArg.
func (p *pp) printValue(value reflect.Value, verb rune, depth int) {
// Handle values with special methods if not already handled by printArg (depth == 0).
- if depth > 0 && value.IsValid() && value.CanInterface() {
- p.arg = value.Interface()
- if p.handleMethods(verb) {
+ if depth > 0 && value.IsValid() {
+ t := value.Type()
+ if p.handleSpecialValues(value, t, verb, depth) {
return
}
+
+ if safeTypeRegistry[t] {
+ defer p.startSafeOverride().restore()
+ }
+
+ if value.CanInterface() {
+ p.arg = value.Interface()
+ if _, ok := p.arg.(i.SafeValue); ok {
+ defer p.startSafeOverride().restore()
+ }
+ if p.handleMethods(verb) {
+ return
+ }
+ }
}
p.arg = nil
p.value = value
@@ -946,7 +1061,9 @@
// argNumber returns the next argument to evaluate, which is either the value of the passed-in
// argNum or the value of the bracketed integer that begins format[i:]. It also returns
// the new value of i, that is, the index of the next byte of the format to process.
-func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) {
+func (p *pp) argNumber(
+ argNum int, format string, i int, numArgs int,
+) (newArgNum, newi int, found bool) {
if len(format) <= i || format[i] != '[' {
return argNum, i, false
}
@@ -972,6 +1089,7 @@
}
func (p *pp) doPrintf(format string, a []interface{}) {
+ p.buf.SetMode(b.SafeEscaped)
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
@@ -1147,6 +1265,7 @@
}
func (p *pp) doPrint(a []interface{}) {
+ p.buf.SetMode(b.SafeEscaped)
prevString := false
for argNum, arg := range a {
isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
@@ -1162,6 +1281,7 @@
// doPrintln is like doPrint but always adds a space between arguments
// and a newline after the last argument.
func (p *pp) doPrintln(a []interface{}) {
+ p.buf.SetMode(b.SafeEscaped)
for argNum, arg := range a {
if argNum > 0 {
p.buf.writeByte(' ')

View File

@@ -0,0 +1,97 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package rfmt
import i "github.com/cockroachdb/redact/interfaces"
// SafeString implements SafePrinter.
func (p *pp) SafeString(s i.SafeString) {
defer p.startSafeOverride().restore()
p.buf.WriteString(string(s))
}
// SafeInt implements SafePrinter.
func (p *pp) SafeInt(s i.SafeInt) {
defer p.startSafeOverride().restore()
p.fmtInteger(uint64(s), signed, 'd')
}
// SafeUint implements SafePrinter.
func (p *pp) SafeUint(s i.SafeUint) {
defer p.startSafeOverride().restore()
p.fmtInteger(uint64(s), unsigned, 'd')
}
// SafeFloat implements SafePrinter.
func (p *pp) SafeFloat(s i.SafeFloat) {
defer p.startSafeOverride().restore()
p.fmtFloat(float64(s), 64, 'v')
}
// SafeRune implements SafePrinter.
func (p *pp) SafeRune(r i.SafeRune) {
defer p.startSafeOverride().restore()
p.buf.WriteRune(rune(r))
}
// SafeByte implements SafePrinter.
func (p *pp) SafeByte(r i.SafeByte) {
defer p.startSafeOverride().restore()
p.buf.WriteByte(byte(r))
}
// SafeBytes implements SafePrinter.
func (p *pp) SafeBytes(r i.SafeBytes) {
defer p.startSafeOverride().restore()
p.buf.Write(r)
}
func (p *pp) Print(args ...interface{}) {
defer p.buf.SetMode(p.buf.GetMode())
np := newPrinter()
np.buf = p.buf
np.doPrint(args)
p.buf = np.buf
np.buf = buffer{}
np.free()
}
func (p *pp) Printf(format string, arg ...interface{}) {
defer p.buf.SetMode(p.buf.GetMode())
np := newPrinter()
np.buf = p.buf
np.doPrintf(format, arg)
p.buf = np.buf
np.buf = buffer{}
np.free()
}
func (p *pp) UnsafeString(s string) {
defer p.startUnsafe().restore()
_, _ = p.buf.WriteString(s)
}
func (p *pp) UnsafeByte(bb byte) {
defer p.startUnsafe().restore()
_ = p.buf.WriteByte(bb)
}
func (p *pp) UnsafeBytes(bs []byte) {
defer p.startUnsafe().restore()
_, _ = p.buf.Write(bs)
}
func (p *pp) UnsafeRune(r rune) {
defer p.startUnsafe().restore()
_ = p.buf.WriteRune(r)
}

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# This file re-generates the sources in this directory from the Go
# standard library.
#
set -euxo pipefail
cp $GOROOT/src/fmt/format.go format.go
patch -p0 <format.go.diff
cp $GOROOT/src/fmt/print.go print.go
patch -p0 <print.go.diff
mkdir -p fmtsort
for a in $GOROOT/src/internal/fmtsort/*; do
if expr "$a" : ".*_test.go"; then
continue
fi
n=$(basename "$a")
(
echo "// Code generated from the Go standard library. DO NOT EDIT"
echo "// GENERATED FILE DO NOT EDIT"
cat "$a"
) >fmtsort/"$n"
done

View File

@@ -0,0 +1,46 @@
// Copyright 2020 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package rfmt
import (
"reflect"
i "github.com/cockroachdb/redact/interfaces"
)
// RegisterSafeType registers a data type to always be considered safe
// during the production of redactable strings.
func RegisterSafeType(t reflect.Type) {
safeTypeRegistry[t] = true
}
// safeTypeRegistry registers Go data types that are to be always
// considered safe, even when they don't implement SafeValue.
var safeTypeRegistry = map[reflect.Type]bool{}
func isSafeValue(a interface{}) bool {
return safeTypeRegistry[reflect.TypeOf(a)]
}
// redactErrorFn can be injected from an error library
// to render error objects safely.
var redactErrorFn func(err error, p i.SafePrinter, verb rune)
// RegisterRedactErrorFn registers an error redaction function for use
// during automatic redaction by this package.
// Provided e.g. by cockroachdb/errors.
func RegisterRedactErrorFn(fn func(err error, p i.SafePrinter, verb rune)) {
redactErrorFn = fn
}