Почему этот код Golang может пропустить значение переменной, которая находится в памяти - PullRequest
4 голосов
/ 06 октября 2019

Этот код может утечь значение переменной, которая находится в памяти.

Я думаю, что, возможно, fmt.XprintY не сбрасывает буфер, но моя попытка отладки бесплодна.

package main

import (
    "bytes"
    "fmt"
    "io"
    "text/template"
)

type SecWriter struct {
    w io.Writer
}

func (s *SecWriter) Write(p []byte) (n int, err error) {
    fmt.Println(string(p), len(p), cap(p))

    // here
    tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")
    if tmp == ""{}

    s.w.Write(p[:64])
    return 64, nil
}

func index() {
    exp := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA{{1}}"

    b := &bytes.Buffer{}
    s := &SecWriter{
        w: b,
    }


    t := template.Must(template.New("index").Parse(exp))
    t.Execute(s, nil)

    fmt.Println("buf: ", b.String())
}

func main() {
    index()
}

My go env:

set GOARCH=amd64
set GOOS=windows

go version

go version go1.12.5 windows/amd64

И вывод:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 64
1 1 128
buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1nfo{SSSSSSSSSSSSSSSSSSSSSSSSSSS}                 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Как видите, частьзначение переменной в памяти:

tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")

Утечка в буфер.

Ответы [ 3 ]

3 голосов
/ 06 октября 2019

Существует нет утечки памяти , , это подтверждает это.
Но есть проблема : вызов p := newPrinter() инициализирован здесь p.fmt.init(&p.buf) in func Fprint(w io.Writer, a ...interface{}) (n int, err error) возвращает свободную память (базовый массив среза), не инициализируя ее нулем (что, вероятно, не инициализировано по соображениям производительности - , который мы ожидали равным нулю ).

TL; DR:
Два решения:
1. Обходной путь: используйте s.w.Write(p) вместо s.w.Write(p[:64]) или измените код и установите p[len(p):cap(p)] все в ноль (если вы не можете или не можете коснуться2-е решение):

func (s *SecWriter) Write(p []byte) (n int, err error) {
    b := p[len(p):cap(p)]
    for i := range b {
        b[i] = 0
    }
    fmt.Println(string(p), len(p), cap(p))
    // here
    tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")
    if tmp == "" {
    }
    s.w.Write(p[:64])
    return 64, nil
}

В Windows (C:\Go\src\fmt\format.go) или Linux (/usr/local/go/src/fmt/format.go) файл в строке 58 задает для буфера все нули:
    b := (*buf)[:cap(*buf)]
    for i := range b {
        b[i] = 0
    }

Внутри этой функции:

func (f *fmt) init(buf *buffer) {
    b := (*buf)[:cap(*buf)]
    for i := range b {
        b[i] = 0
    }
    f.buf = buf
    f.clearflags()
}

Ваш код выводится с этим приложением:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 64
1 1 128
buf:  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1

Длинный ответ:
Вы просматриваете данные среза сверх назначенной длины, и вам разрешено просматривать данные среза вемкость среза.
Вы можете заменить: m.Writer.Write(p[:8]) на: m.Writer.Write(p), что заставляет ваш код работать правильно. Следующий код показывает, что template.Parse() разбивает шаблон на 3 части и вызывает my.Write() три раза. интересная часть здесь - это второй вызов my.Write(), показывающий сгенерированный компилятором срез с различной емкостью среза, который не инициализирован на ноль, «возможно, это незначительная безвредная проблема ":

Если вы хотите шпионить за памятью вашего компьютера, попробуйте this :

package main

import (
    "bytes"
    "fmt"
    "io"
    "text/template"
)

func main() {
    buf := &bytes.Buffer{}
    my := &myWriter{"You", buf}
    template.Must(template.New("my").Parse("Hi{{.Name}}Bye.")).Execute(my, my)
    fmt.Printf("<<%q>>\n", buf.String())
}
func (m *myWriter) Write(p []byte) (n int, err error) {
    fmt.Printf("len=%v cap=%v\t%v %v\n", len(p), cap(p), string(p), p[:cap(p)])
    no++
    fmt.Println("gen:", no, gen())
    m.Writer.Write(p)
    // m.Writer.Write(p[:8])
    return 8, nil
}

type myWriter struct {
    Name string
    io.Writer
}

const genLen = 8

func gen() string {
    b := [genLen]byte{}
    for i := range b {
        b[i] = no
    }
    return string(b[:])
}

var no = byte(49) //'1'

Вывод:

len=2 cap=8 Hi [72 105 0 0 0 0 0 0]
gen: 50 22222222
len=3 cap=64    You [89 111 117 58 32 53 48 32 50 50 50 50 50 50 50 50 10 50 32 49 48 53 32 48 32 48 32 48 32 48 32 48 32 48 93 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
gen: 51 33333333
len=4 cap=8 Bye. [66 121 101 46 0 0 0 0]
gen: 52 44444444
<<"HiYouBye.">>

И измените const genLen = 64 try this intersting : cap=64 изменяется на cap=128 (что не ожидается):

Вывод:

len=2 cap=8 Hi [72 105 0 0 0 0 0 0]
gen: 50 2222222222222222222222222222222222222222222222222222222222222222
len=3 cap=128   You [89 111 117 58 32 53 48 32 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
gen: 51 3333333333333333333333333333333333333333333333333333333333333333
len=4 cap=8 Bye. [66 121 101 46 0 0 0 0]
gen: 52 4444444444444444444444444444444444444444444444444444444444444444
<<"HiYouBye.">>

t.Execute(my, my) вызывает func (m *myWriter) Write(p []byte), поэтому p с len=3 и cap=128, сгенерированным механизмом тамплат.

После отладки 2-го кода внутри файла /usr/local/go/src/fmt/print.go в строке 230кажется, это fmt.buffer с length=3 и cap=128, здесь:

func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrint(a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

Вызов p := newPrinter() инициализирован здесь p.fmt.init(&p.buf):

// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
    p := ppFree.Get().(*pp)
    p.panicking = false
    p.erroring = false
    p.wrapErrs = false
    p.fmt.init(&p.buf)
    return p
}

Получает и возвращает свободную память без инициализации ее нуля.

3 голосов
/ 06 октября 2019

В Go выражение s.w.Write(p[:64]) может расширить срез за пределы его длины без ошибки (до емкости среза). В этом случае указанная длина буфера составляет всего 1, но вы увеличили ее до 64 (как во второй строке вывода). Содержимое в дополнительных 63 байтах не определено, и это результат вывода некоторых fmt методов.

Решение - проверить длину среза. Если вы хотите сделать срез устойчивым к ошибкам и убедиться, что содержимое невозможно увидеть за пределами его длины, вы можете использовать трехиндексный синтаксис для срезов, чтобы задать его емкость, например p = p[::len(p)].

0 голосов
/ 06 октября 2019

Если вы присваиваете переменную напрямую, а не fmt.Sprintln, переменная не будет течь.

Код: https://play.golang.org/p/Nz0y_MfDjP1

Поэтому я считаю, fmt.Sprintln вызывает утечку. Эта функция вызывает другую неэкспортированную функцию newPrinter, чтобы получить printer, которая, в свою очередь, поддерживает свой собственный пул и кэши. Я не достаточно углубился, но я думаю, что буфер, который вы создали вручную, может как-то перекрываться / разделяться здесь.

(я обновлю ответ, если найду что-нибудь еще)

...