{"record":{"id":"8cd42c97ba2ead7b","repo":"siyuan-note/siyuan","slug":"empty-heif-image-8cd42c","errorCode":null,"errorMessage":"empty HEIF image","messagePattern":"empty HEIF image","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/heif/convert.go","lineNumber":103,"sourceCode":"\nfunc pixelsWithinBudget(workingBudget, outputReserve, hardLimit int) int {\n\tavailable := workingBudget - 2*MaxInputBytes - outputReserve\n\tif available <= 0 {\n\t\treturn 0\n\t}\n\treturn min(available/workingBytesPerPixel, hardLimit)\n}\n\ntype Mode string\n\nconst (\n\tModePreview   Mode = \"preview\"\n\tModeThumbnail Mode = \"thumb\"\n)\n\nfunc convert(ctx context.Context, source []byte, mode Mode) ([]byte, error) {\n\tif len(source) == 0 {\n\t\treturn nil, errors.New(\"empty HEIF image\")\n\t}\n\tif len(source) > MaxInputBytes {\n\t\treturn nil, ErrInputTooLarge\n\t}\n\tif mode != ModePreview && mode != ModeThumbnail {\n\t\treturn nil, ErrInvalidMode\n\t}\n\n\tselect {\n\tcase conversionSlots <- struct{}{}:\n\t\tdefer func() {\n\t\t\t<-conversionSlots\n\t\t}()\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n\tconversionContext, cancel := context.WithTimeout(ctx, conversionTimeout)\n\tdefer cancel()","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/heif/convert.go#L85-L121","documentation":"convert re-checks that the source byte slice is non-empty before decoding, failing with 'empty HEIF image'. This is the lower-level duplicate of the GetOrCreate guard: even direct callers of convert must supply actual image bytes. The checks in convert run in order: emptiness, size limit (ErrInputTooLarge), then mode validity (ErrInvalidMode).","triggerScenarios":"Calling the internal convert(ctx, source, mode) with nil/empty source — e.g. an anonymous caller wrapping convert in a goroutine that received an empty buffer from a failed read or an upstream GetOrCreate path whose validation was bypassed.","commonSituations":"Worker/goroutine pipelines where an empty read result is forwarded to convert without checking len; refactors that add a new entry point into convert without the GetOrCreate pre-validation.","solutions":["Validate len(source) > 0 in the caller before dispatching conversion work to the goroutine.","Propagate read errors: never send the buffer downstream when the file/asset read failed.","Route all conversion through heif.GetOrCreate so the unified validation (mode, BoxID, emptiness, size) runs first."],"exampleFix":"// before\ngo func() { out, err := convert(ctx, buf, mode) }() // buf may be empty\n// after\nif len(buf) == 0 {\n    errCh <- errors.New(\"skip conversion: empty source\")\n    return\n}\ngo func() { out, err := convert(ctx, buf, mode) }()","handlingStrategy":"validation","validationCode":"if len(buf) == 0 {\n    return errors.New(\"refusing conversion: source buffer is empty\")\n}\nif len(buf) > heif.MaxInputBytes {\n    return heif.ErrInputTooLarge\n}","typeGuard":null,"tryCatchPattern":"out, err := convert(ctx, buf, mode)\nif err != nil && strings.Contains(err.Error(), \"empty HEIF image\") {\n    return fmt.Errorf(\"conversion skipped: no image bytes provided: %w\", err)\n}","preventionTips":["Check len(source) before dispatching conversion work to goroutines.","Propagate read errors instead of forwarding empty buffers downstream.","Prefer heif.GetOrCreate over calling convert directly so all validations run.","Treat an empty conversion result queue item as a signal to re-fetch the asset."],"tags":["heif","empty-input","validation"],"backgroundTag":"empty-required-field","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}