Go编码指南·内存管理篇

1.1.1【必须】切片长度校验

  • 在对slice进行操作时,必须判断长度是否合法,防止程序panic
// bad: 未判断data的长度,可导致 index out of range
func decode(data []byte) bool {
	if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
		fmt.Println("Bad")
		return true
	}
	return false
}

// bad: slice bounds out of range
func foo() {
	var slice = []int{0, 1, 2, 3, 4, 5, 6}
	fmt.Println(slice[:10])
}

// good: 使用data前应判断长度是否合法
func decode(data []byte) bool {
	if len(data) == 6 {
		if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
			fmt.Println("Good")
			return true
		}
	}
	return false
}

1.1.2【必须】nil指针判断

  • 进行指针操作时,必须判断该指针是否为nil,防止程序panic,尤其在进行结构体Unmarshal时
type Packet struct {
	PackeyType    uint8
	PackeyVersion uint8
	Data          *Data
}

type Data struct {
	Stat uint8
	Len  uint8
	Buf  [8]byte
}

func (p *Packet) UnmarshalBinary(b []byte) error {
	if len(b) < 2 {
		return io.EOF
	}

	p.PackeyType = b[0]
	p.PackeyVersion = b[1]

	// 若长度等于2,那么不会new Data
	if len(b) > 2 {
		p.Data = new(Data)
	}
	return nil
}

// bad: 未判断指针是否为nil
func main() {
	packet := new(Packet)
	data := make([]byte, 2)
	if err := packet.UnmarshalBinary(data); err != nil {
		fmt.Println("Failed to unmarshal packet")
		return
	}

	fmt.Printf("Stat: %v\n", packet.Data.Stat)
}

// good: 判断Data指针是否为nil
func main() {
	packet := new(Packet)
	data := make([]byte, 2)

	if err := packet.UnmarshalBinary(data); err != nil {
		fmt.Println("Failed to unmarshal packet")
		return
	}

	if packet.Data == nil {
		return
	}

	fmt.Printf("Stat: %v\n", packet.Data.Stat)
}

1.1.3【必须】整数安全

  • 在进行数字运算操作时,需要做好长度限制,防止外部输入运算导致异常:

    • 确保无符号整数运算时不会反转
    • 确保有符号整数运算时不会出现溢出
    • 确保整型转换时不会出现截断错误
    • 确保整型转换时不会出现符号错误
  • 以下场景必须严格进行长度限制:

    • 作为数组索引
    • 作为对象的长度或者大小
    • 作为数组的边界(如作为循环计数器)
// bad: 未限制长度,导致整数溢出
func overflow(numControlByUser int32) {
	var numInt int32 = 0
	numInt = numControlByUser + 1
	// 对长度限制不当,导致整数溢出
	fmt.Printf("%d\n", numInt)
	// 使用numInt,可能导致其他错误
}

func main() {
	overflow(2147483647)
}

// good
func overflow(numControlByUser int32) {
	var numInt int32 = 0
	numInt = numControlByUser + 1
	if numInt < 0 {
		fmt.Println("integer overflow")
		return
	}
	fmt.Println("integer ok")
}

func main() {
	overflow(2147483647)
}

1.1.4【必须】make分配长度验证

  • 在进行make分配内存时,需要对外部可控的长度进行校验,防止程序panic。
// bad
func parse(lenControlByUser int, data []byte) {
	size := lenControlByUser
	// 对外部传入的size,进行长度判断以免导致panic
	buffer := make([]byte, size)
	copy(buffer, data)
}

// good
func parse(lenControlByUser int, data []byte) ([]byte, error) {
	size := lenControlByUser
	// 限制外部可控的长度大小范围
	if size > 64*1024*1024 {
		return nil, errors.New("value too large")
	}
	buffer := make([]byte, size)
	copy(buffer, data)
	return buffer, nil
}

1.1.5【必须】禁止SetFinalizer和指针循环引用同时使用

  • 当一个对象从被GC选中到移除内存之前,runtime.SetFinalizer()都不会执行,即使程序正常结束或者发生错误。由指针构成的“循环引用”虽然能被GC正确处理,但由于无法确定Finalizer依赖顺序,从而无法调用runtime.SetFinalizer(),导致目标对象无法变成可达状态,从而造成内存无法被回收。
// bad
func foo() {
	var a, b Data
	a.o = &b
	b.o = &a

	// 指针循环引用,SetFinalizer()无法正常调用
	runtime.SetFinalizer(&a, func(d *Data) {
		fmt.Printf("a %p final.\n", d)
	})
	runtime.SetFinalizer(&b, func(d *Data) {
		fmt.Printf("b %p final.\n", d)
	})
}

func main() {
	for {
		foo()
		time.Sleep(time.Millisecond)
	}
}

1.1.6【必须】禁止重复释放channel

  • 重复释放一般存在于异常流程判断中,如果恶意攻击者构造出异常条件使程序重复释放channel,则会触发运行时panic,从而造成DoS攻击。
// bad
func foo(c chan int) {
	defer close(c)
	err := processBusiness()
	if err != nil {
		c <- 0
		close(c) // 重复释放channel
		return
	}
	c <- 1
}

// good
func foo(c chan int) {
	defer close(c) // 使用defer延迟关闭channel
	err := processBusiness()
	if err != nil {
		c <- 0
		return
	}
	c <- 1
}

1.1.7【必须】确保每个协程都能退出

  • 启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。
// bad: 协程没有设置退出条件
func doWaiter(name string, second int) {
	for {
		time.Sleep(time.Duration(second) * time.Second)
		fmt.Println(name, " is ready!")
	}
}

1.1.8【推荐】不使用unsafe包

  • 由于unsafe包绕过了 Golang 的内存安全原则,一般来说使用该库是不安全的,可导致内存破坏,尽量避免使用该包。若必须要使用unsafe操作指针,必须做好安全校验。
// bad: 通过unsafe操作原始指针
func unsafePointer() {
	b := make([]byte, 1)
	foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
	fmt.Print(*foo + 1)
}

// [signal SIGSEGV: segmentation violation code=0x1 addr=0xc100068f55 pc=0x49142b]

1.1.9【推荐】不使用slice作为函数入参

  • slice在作为函数入参时,函数内对slice的修改可能会影响原始数据
  // bad
  // slice作为函数入参时包含原始数组指针
  func modify(array []int) {
      array[0] = 10 // 对入参slice的元素修改会影响原始数据
  }
  
  func main() {
      array := []int{1, 2, 3, 4, 5}
  
      modify(array)
      fmt.Println(array) // output:[10 2 3 4 5]
  }

  // good
  // 数组作为函数入参,而不是slice
  func modify(array [5]int) {
    array[0] = 10
  }

  func main() {
      // 传入数组,注意数组与slice的区别
      array := [5]int{1, 2, 3, 4, 5}
  
      modify(array)
      fmt.Println(array)
  }
  

你可能感兴趣的:(golang,网络,开发语言)