Go
Go panic runtime error invalid memory address or nil pointer dereference
Encountering a Go: panic: runtime error: invalid memory address or nil pointer dereference can be one of the most frustrating experiences for a Go developer. This particular panic is a common culprit behind unexpected program crashes, signaling that your application tried to access a memory location through a pointer that wasn’t pointing to anything valid. Essentially, your code attempted to use a reference that was nil, which in Go means “no value,” leading the runtime to halt execution to prevent further memory corruption or unpredictable behavior. Understanding the root causes and implementing robust strategies for prevention and debugging are crucial skills for any Go programmer looking to build resilient and stable applications. This article will demystify this dreaded error, walk you through its common triggers, and equip you with the knowledge to debug and ultimately prevent it, ensuring your Go programs run smoothly and reliably.
Understanding the Error: Go: panic: runtime error: invalid memory address or nil pointer dereference
The message Go: panic: runtime error: invalid memory address or nil pointer dereference is a clear signal from the Go runtime that something has gone fundamentally wrong with how your program is handling memory references. In Go, a “panic” is a special kind of runtime error that stops the normal flow of execution. Unlike an ordinary error, which you’re expected to handle gracefully, a panic typically indicates an unrecoverable bug in your program or a situation where the program cannot continue safely.
The core of this panic lies in the concept of a “pointer.” A pointer is a variable that stores the memory address of another variable. When you “dereference” a pointer, you are trying to access the value stored at the memory address it holds. The problem arises when this pointer holds a nil value. In Go, nil is the zero value for pointers, interfaces, maps, slices, channels, and functions. A nil pointer doesn’t point to any valid memory location. Therefore, attempting to dereference a nil pointer—for example, by trying to access a field of a struct through a nil pointer or calling a method on a nil interface—is akin to trying to open a door that doesn’t exist. The Go runtime catches this illegal operation and triggers the nil pointer dereference panic.
This runtime error is a critical form of memory access violation. It happens because the program expects a valid object at a certain memory address but finds nothing, or rather, finds the special nil value indicating no object. Without this explicit check, the program might attempt to read from or write to an arbitrary memory location, leading to unpredictable behavior, data corruption, or even security vulnerabilities. The Go runtime’s quick termination via panic is a safety mechanism to prevent these more severe consequences, making it imperative for developers to understand and address the underlying cause.
The Go: panic: runtime error: invalid memory address or nil pointer dereference occurs when a program attempts to access or modify data through a pointer that currently holds a nil value. In Go, nil signifies the absence of a value for types like pointers, slices, maps, channels, functions, and interfaces. When code tries to use a nil pointer as if it points to a valid memory location (e.g., calling a method on it or accessing a field), the Go runtime detects this invalid operation and terminates the program with a panic.
Common Causes and Scenarios Leading to Nil Pointer Dereference
Understanding the “how” behind nil pointer dereference is crucial for effective prevention. This panic often stems from predictable patterns and oversights in code. One of the most frequent causes is failing to initialize a pointer before attempting to use it. For instance, declaring a variable of a pointer type (e.g., MyStruct) without assigning it a value using new() or an address-of operator (&) will leave it as nil. Any subsequent attempt to access fields or call methods on this uninitialized pointer will result in a panic. This often happens with struct fields that are themselves pointers and are not explicitly initialized when the parent struct is created.
Another common scenario involves accessing elements from maps or slices without proper checks. When you try to retrieve a value from a map using a key that doesn’t exist, Go returns the zero value for the element type. If that element type is a pointer, you’ll get nil. Similarly, operating on a slice that is nil or accessing an index out of bounds can lead to similar memory access issues. For example, if a function returns a nil slice, and you immediately try to append to it without checking if it’s nil, it can cause problems, though append on a nil slice works correctly, many other slice operations do not.
Furthermore, type assertions on interfaces that hold a nil value are a frequent source of this panic. If an interface variable is nil, and you try to assert it to a concrete type (e.g., myVar.(MyType)), it will panic. Proper error handling and checks are essential here. Concurrency issues can also introduce subtle nil pointer problems. In concurrent programs, a pointer might be valid at one point, but another goroutine could set it to nil before the first goroutine attempts to use it, creating a race condition that results in a dereferencing nil situation. This highlights the need for careful synchronization and defensive Question & Answer :
When running my Go program, it panics and returns the following:
panic: runtime error: invalid memory address or nil pointer dereference [signal 0xb code=0x1 addr=0x38 pc=0x26df] goroutine 1 [running]: main.getBody(0x1cdcd4, 0xf800000004, 0x1f2b44, 0x23, 0xf84005c800, ...) /Users/matt/Dropbox/code/go/scripts/cron/fido.go:65 +0x2bb main.getToken(0xf84005c7e0, 0x10) /Users/matt/Dropbox/code/go/scripts/cron/fido.go:140 +0x156 main.main() /Users/matt/Dropbox/code/go/scripts/cron/fido.go:178 +0x61 goroutine 2 [syscall]: created by runtime.main /usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:221 goroutine 3 [syscall]: syscall.Syscall6() /usr/local/Cellar/go/1.0.3/src/pkg/syscall/asm_darwin_amd64.s:38 +0x5 syscall.kevent(0x6, 0x0, 0x0, 0xf840085188, 0xa, ...) /usr/local/Cellar/go/1.0.3/src/pkg/syscall/zsyscall_darwin_amd64.go:199 +0x88 syscall.Kevent(0xf800000006, 0x0, 0x0, 0xf840085188, 0xa0000000a, ...) /usr/local/Cellar/go/1.0.3/src/pkg/syscall/syscall_bsd.go:546 +0xa4 net.(*pollster).WaitFD(0xf840085180, 0xf840059040, 0x0, 0x0, 0x0, ...) /usr/local/Cellar/go/1.0.3/src/pkg/net/fd_darwin.go:96 +0x185 net.(*pollServer).Run(0xf840059040, 0x0) /usr/local/Cellar/go/1.0.3/src/pkg/net/fd.go:236 +0xe4 created by net.newPollServer /usr/local/Cellar/go/1.0.3/src/pkg/net/newpollserver.go:35 +0x382
I’ve looked at the responses others have had to the same exception, but can’t see anything simple (i.e. an unhandled error).
I am running it on a machine that does not have access to the API servers listed in the code, but I was hoping it’d return an appropriate error (as I’ve attempted to catch errors of that kind).
package main /* Fido fetches the list of public images from the Glance server, captures the IDs of images with 'status': 'active' and then queues the images for pre-fetching with the Glance CLI utility `glance-cache-manage`. Once the images are added to the queue, `glance-cache-prefetcher` is called to actively fetch the queued images into the local compute nodes' image cache. See http://docs.openstack.org/developer/glance/cache.html for further details on the Glance image cache. */ import ( "bytes" "encoding/json" "fmt" "io/ioutil" /* "log" "log/syslog" */ "net/http" "os" "os/exec" ) func prefetchImages() error { cmd := exec.Command("glance-cache-prefetcher") err := cmd.Run() if err != nil { return fmt.Errorf("glance-cache-prefetcher failed to execute properly: %v", err) } return nil } func queueImages(hostname string, imageList []string) error { for _, image := range imageList { cmd := exec.Command("glance-cache-manage", "--host=", hostname, "queue-image", image) err := cmd.Run() if err != nil { return fmt.Errorf("glance-cache-manage failed to execute properly: %v", err) } else { fmt.Printf("Image %s queued", image) } } return nil } func getBody(method string, url string, headers map[string]string, body []byte) ([]byte, error) { client := &http.Client{} req, err := http.NewRequest(method, url, bytes.NewReader(body)) if err != nil { return nil, err } for key, value := range headers { req.Header.Add(key, value) } res, err := client.Do(req) defer res.Body.Close() if err != nil { return nil, err } var bodyBytes []byte if res.StatusCode == 200 { bodyBytes, err = ioutil.ReadAll(res.Body) } else if err != nil { return nil, err } else { return nil, fmt.Errorf("The remote end did not return a HTTP 200 (OK) response.") } return bodyBytes, nil } func getImages(authToken string) ([]string, error) { type GlanceDetailResponse struct { Images []struct { Name string `json:"name"` Status string `json:"status"` ID string `json:"id"` } } method := "GET" url := "http://192.168.1.2:9292/v1.1/images/detail" headers := map[string]string{"X-Auth-Token": authToken} bodyBytes, err := getBody(method, url, headers, nil) if err != nil { return nil, fmt.Errorf("unable to retrieve the response body from the Glance API server: %v", err) } var glance GlanceDetailResponse err = json.Unmarshal(bodyBytes, &glance) if err != nil { return nil, fmt.Errorf("unable to parse the JSON response:", err) } imageList := make([]string, 10) for _, image := range glance.Images { if image.Status == "active" { imageList = append(imageList, image.ID) } } return imageList, nil } func getToken() (string, error) { type TokenResponse struct { Auth []struct { Token struct { Expires string `json:"expires"` ID string `json:"id"` } } } method := "POST" url := "http://192.168.1.2:5000/v2.0/tokens" headers := map[string]string{"Content-type": "application/json"} creds := []byte(`{"auth":{"passwordCredentials":{"username": "glance", "password":"<password>"}, "tenantId":"<tenantkeygoeshere>"}}`) bodyBytes, err := getBody(method, url, headers, creds) if err != nil { return "", err } var keystone TokenResponse err = json.Unmarshal(bodyBytes, &keystone) if err != nil { return "", err } authToken := string((keystone.Auth[0].Token.ID)) return authToken, nil } func main() { /* slog, err := syslog.New(syslog.LOG_ERR, "[fido]") if err != nil { log.Fatalf("unable to connect to syslog: %v", err) os.Exit(1) } else { defer slog.Close() } */ hostname, err := os.Hostname() if err != nil { // slog.Err("Hostname not captured") os.Exit(1) } authToken, err := getToken() if err != nil { // slog.Err("The authentication token from the Glance API server was not retrieved") os.Exit(1) } imageList, err := getImages(authToken) err = queueImages(hostname, imageList) if err != nil { // slog.Err("Could not queue the images for pre-fetching") os.Exit(1) } err = prefetchImages() if err != nil { // slog.Err("Could not queue the images for pre-fetching") os.Exit(1) } return }
According to the docs for func (*Client) Do:
“An error is returned if caused by client policy (such as CheckRedirect), or if there was an HTTP protocol error. A non-2xx response doesn’t cause an error.
When err is nil, resp always contains a non-nil resp.Body.”
Then looking at this code:
res, err := client.Do(req) defer res.Body.Close() if err != nil { return nil, err }
I’m guessing that err is not nil. You’re accessing the .Close() method on res.Body before you check for the err.
The defer only defers the function call. The field and method are accessed immediately.
So instead, try checking the error immediately.
res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close()