When does defer function executes?
The standard answer is: defer
runs a function before the enclosing function returns.
It is a wrong explanation. The defer
executes as follows:
- The enclosing function has processed returned values and stored them in registers
- All defer functions run in a LIFO order of definition.
- If the return parameters are named, the defer function can alter the return values.
Short Examples on Vulnerabilities in defer Code
defer Modifying Named Return Parameters
package main
import (
"fmt"
"sync"
)
func test4(x int) (y int) {
defer func() {
y = 100
}()
if x == 123 {
return x
}
defer func() {
y = 200
}()
return x
}
func main() {
fmt.Printf("Hello x=%v\n", test4(1234))
}
The above code returns value 123 referenced by y
. Now, defer functions start executing. Mind it, the function test4()
is yet to return to main()
.
- The last defer function modifies the return value
y
to 200.
- The first defer function modifies the return value
y
to 100.
test4()
returns 100.
Deadlocked Program with Improper Understanding of defer
func test5(abort bool) {
var mutex = &sync.Mutex{}
defer func() {
fmt.Printf("locking the mutex-first\n")
mutex.Unlock()
}()
if abort {
mutex.Lock()
fmt.Println("critical section")
mutex.Unlock()
return
}
defer func() {
fmt.Printf("locking the mutex-second\n")
mutex.Lock()
}()
}
func main() {
test5(true)
}
go run deferDetails/main.go
critical section
locking the mutex-first
fatal error: sync: unlock of unlocked mutex
goroutine 1 [running]:
runtime.throw(0x10d2d55, 0x1e)
/Users/ovo/.goenv/versions/1.14.0/src/runtime/panic.go:1112 +0x72 fp=0xc000078e38 sp=0xc000078e08 pc=0x102e542
sync.throw(0x10d2d55, 0x1e)
/Users/ovo/.goenv/versions/1.14.0/src/runtime/panic.go:1098 +0x35 fp=0xc000078e58 sp=0xc000078e38 pc=0x102e4c5
sync.(*Mutex).unlockSlow(0xc00018c008, 0xc0ffffffff)
/Users/ovo/.goenv/versions/1.14.0/src/sync/mutex.go:196 +0xd6 fp=0xc000078e80 sp=0xc000078e58 pc=0x106a746
sync.(*Mutex).Unlock(...)
/Users/ovo/.goenv/versions/1.14.0/src/sync/mutex.go:190
main.test5.func1(0xc00018c008)
/Users/ovo/go/1.14.0/src/mygo/deferDetails/main.go:13 +0x8b fp=0xc000078ee0 sp=0xc000078e80 pc=0x109ec7b
main.test5(0xc00006c001)
/Users/ovo/go/1.14.0/src/mygo/deferDetails/main.go:20 +0x11a fp=0xc000078f70 sp=0xc000078ee0 pc=0x109eb1a
main.main()
/Users/ovo/go/1.14.0/src/mygo/deferDetails/main.go:31 +0x26 fp=0xc000078f88 sp=0xc000078f70 pc=0x109ebd6
runtime.main()
Explanation
ha! the defer caused a panic in the program. I am not quite sure why it failed. If I remove the return
, the code works.
package main
import (
"fmt"
"sync"
)
func test5(abort bool) {
var mutex = &sync.Mutex{}
defer func() {
fmt.Printf("locking the mutex-first\n")
mutex.Unlock()
}()
if abort {
mutex.Lock()
fmt.Println("critical section")
mutex.Unlock()
}
defer func() {
fmt.Printf("locking the mutex-second\n")
mutex.Lock()
}()
}
func main() {
test5(true)
}
go run deferDetails/main.go
critical section
locking the mutex-second
locking the mutex-first
Conclusion
- Use defer very carefully, especially around named return parameter
- Multiple defer statements are executed in reverse order of appearance.
- Need to root cause why the code with
return
panic.
References
Written with StackEdit.