{"record":{"id":"5f6728c323671d56","repo":"wailsapp/wails","slug":"invalid-json-passed-to-notify-notifymessage","errorCode":null,"errorMessage":"Invalid JSON passed to Notify: ${notifyMessage}","messagePattern":"Invalid JSON passed to Notify: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/runtime/desktop/events.js","lineNumber":132,"sourceCode":"        }\n    }\n}\n\n/**\n * Notify informs frontend listeners that an event was emitted with the given data\n *\n * @export\n * @param {string} notifyMessage - encoded notification message\n\n */\nexport function EventsNotify(notifyMessage) {\n    // Parse the message\n    let message;\n    try {\n        message = JSON.parse(notifyMessage);\n    } catch (e) {\n        const error = 'Invalid JSON passed to Notify: ' + notifyMessage;\n        throw new Error(error);\n    }\n    notifyListeners(message);\n}\n\n/**\n * Emit an event with the given name and data\n *\n * @export\n * @param {string} eventName\n */\nexport function EventsEmit(eventName) {\n\n    const payload = {\n        name: eventName,\n        data: [].slice.apply(arguments).slice(1),\n    };\n\n    // Notify JS listeners","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/runtime/desktop/events.js#L114-L150","documentation":"SelectObject wraps gdi32.SelectObject, which selects a GDI object (pen, brush, font, bitmap) into a device context and returns the previously selected object. The w32 wrapper panics with 'SelectObject failed' when the API returns NULL/0. Windows returns failure when the handle is not a valid GDI object, does not match a type the DC accepts, or has already been deleted — a classic use-after-delete bug. Note the wrapper's zero check is also technically lossy for regions, where the error return is HGDI_ERROR rather than NULL.","triggerScenarios":"Selecting a bitmap/pen/brush that was already passed to DeleteObject; selecting a bitmap into more than one memory DC at a time (GDI forbids this); passing a handle of the wrong type (e.g. an HBRUSH where an HBITMAP is required); using a DC after DeleteDC.","commonSituations":"Double-buffering code that deletes the old bitmap but keeps selecting it again on the next frame; cleanup routines that delete brushes still selected into a live DC; porting drawing code from C where the old object restore order was accidental but tolerated.","solutions":["Ensure the object being selected was created (CreateSolidBrush, CreateCompatibleBitmap, ...) and not deleted; search for DeleteObject calls on shared objects.","Follow the save/restore idiom: store the return value (the old object) and re-select it before deleting your object and the DC.","Never have one HBITMAP selected into two DCs simultaneously; create one compatible bitmap per DC.","Confirm the object type matches what the DC expects at that slot (bitmap only into memory DCs).","Run with Application Verifier or GDI diagnostic tools (e.g. GDIView) to find which handle is dead at selection time."],"exampleFix":"// before\nold := w32.SelectObject(hdcMem, hbm)\nw32.DeleteObject(hbm) // still selected in another DC -> later SelectObject panics\n\n// after\nold := w32.SelectObject(hdcMem, hbm)\ndefer w32.SelectObject(hdcMem, old) // restore first\n// ... draw ...\nw32.DeleteObject(hbm) // delete only after restore","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"func safeSelect(hdc w32.HDC, obj w32.HGDIOBJ) (old w32.HGDIOBJ, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"SelectObject: %v (object deleted or wrong type?)\", r)\n\t\t}\n\t}()\n\treturn w32.SelectObject(hdc, obj), nil\n}","preventionTips":["Always save the returned old object and re-select it before deleting your object or the DC.","Delete a GDI object only when it is not selected into any DC.","Never share one HBITMAP across two memory DCs at the same time."],"tags":["windows","gdi","use-after-free","syscall","panic"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}