Compare commits

..

3 Commits

Author SHA1 Message Date
Lovell Fuller
17d4a684df WIP: Switch from custom VError to standard runtime_error 2026-01-23 22:26:15 +00:00
Lovell Fuller
ed6b7384d0 Ensure TIFF output bitdepth option is limited to 1, 2 or 4 2026-01-23 21:29:40 +00:00
Lovell Fuller
ef77388a73 Force MSVC to use exception handling
As of 8.18.0, libvips C++ wrapper retrieves error messages at
exception construction time rather than lazily when accessed.

On Windows this led to error messages being referenced rather
than copied, leading to access beyond their lifetime and possible
corruption.
2026-01-22 12:52:48 +00:00
10 changed files with 30 additions and 36 deletions

View File

@@ -20,7 +20,7 @@ slug: changelog/v0.35.0
* Upgrade to libvips v8.18.0 for upstream bug fixes. * Upgrade to libvips v8.18.0 for upstream bug fixes.
* Improve thread-safety of error (and warning) messages. * Ensure TIFF output `bitdepth` option is limited to 1, 2 or 4.
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries. * Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.

4
lib/index.d.ts vendored
View File

@@ -1479,8 +1479,8 @@ declare namespace sharp {
xres?: number | undefined; xres?: number | undefined;
/** Vertical resolution in pixels/mm (optional, default 1.0) */ /** Vertical resolution in pixels/mm (optional, default 1.0) */
yres?: number | undefined; yres?: number | undefined;
/** Reduce bitdepth to 1, 2 or 4 bit (optional, default 8) */ /** Reduce bitdepth to 1, 2 or 4 bit (optional) */
bitdepth?: 1 | 2 | 4 | 8 | undefined; bitdepth?: 1 | 2 | 4 | undefined;
/** Write 1-bit images as miniswhite (optional, default false) */ /** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined; miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */ /** Resolution unit options: inch, cm (optional, default 'inch') */

View File

@@ -21,6 +21,7 @@
'defines': [ 'defines': [
'_VIPS_PUBLIC=__declspec(dllexport)', '_VIPS_PUBLIC=__declspec(dllexport)',
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_HAS_EXCEPTIONS=1',
'G_DISABLE_ASSERT', 'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS', 'G_DISABLE_CAST_CHECKS',
'G_DISABLE_CHECKS' 'G_DISABLE_CHECKS'
@@ -148,7 +149,8 @@
['OS == "win"', { ['OS == "win"', {
'defines': [ 'defines': [
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64',
'_HAS_EXCEPTIONS=1'
], ],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [

View File

@@ -152,12 +152,7 @@ class MetadataWorker : public Napi::AsyncWorker {
// PNG comments // PNG comments
vips_image_map(image.get_image(), readPNGComment, &baton->comments); vips_image_map(image.get_image(), readPNGComment, &baton->comments);
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
baton->warnings.push_back(warning);
warning = sharp::VipsWarningPop();
}
// Clean up // Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -167,9 +162,13 @@ class MetadataWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto& warning : baton->warnings) { // Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
Napi::Object info = Napi::Object::New(env); Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format); info.Set("format", baton->format);

View File

@@ -57,7 +57,6 @@ struct MetadataBaton {
size_t gainMapLength; size_t gainMapLength;
MetadataComments comments; MetadataComments comments;
std::string err; std::string err;
std::vector<std::string> warnings;
MetadataBaton(): MetadataBaton():
input(nullptr), input(nullptr),

View File

@@ -1280,21 +1280,12 @@ class PipelineWorker : public Napi::AsyncWorker {
} else { } else {
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) { if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
(baton->err).append("Warning treated as error due to failOn setting"); (baton->err).append("Warning treated as error due to failOn setting");
baton->errUseWarning = true;
} else { } else {
(baton->err).append("Unknown error"); (baton->err).append("Unknown error");
} }
} }
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
(baton->err).append("\n").append(warning);
} else {
(baton->warnings).push_back(warning);
}
warning = sharp::VipsWarningPop();
}
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -1304,8 +1295,15 @@ class PipelineWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto &warning : baton->warnings) { // Handle warnings
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
if (baton->errUseWarning) {
(baton->err).append("\n").append(warning);
} else {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
}
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
int width = baton->width; int width = baton->width;

View File

@@ -204,7 +204,6 @@ struct PipelineBaton {
bool jxlLossless; bool jxlLossless;
VipsBandFormat rawDepth; VipsBandFormat rawDepth;
std::string err; std::string err;
std::vector<std::string> warnings;
bool errUseWarning; bool errUseWarning;
int keepMetadata; int keepMetadata;
int withMetadataOrientation; int withMetadataOrientation;

View File

@@ -96,12 +96,7 @@ class StatsWorker : public Napi::AsyncWorker {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
baton->warnings.push_back(warning);
warning = sharp::VipsWarningPop();
}
// Clean up // Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -111,8 +106,11 @@ class StatsWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto& warning : baton->warnings) { // Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
// Stats Object // Stats Object

View File

@@ -45,7 +45,6 @@ struct StatsBaton {
int dominantBlue; int dominantBlue;
std::string err; std::string err;
std::vector<std::string> warnings;
StatsBaton(): StatsBaton():
input(nullptr), input(nullptr),

View File

@@ -642,7 +642,7 @@ describe('Image metadata', () => {
}); });
it('keep existing ICC profile', async () => { it('keep existing ICC profile', async () => {
const data = await sharp(fixtures.inputJpgWithExif, { failOn: 'error' }) const data = await sharp(fixtures.inputJpgWithExif)
.keepIccProfile() .keepIccProfile()
.toBuffer(); .toBuffer();
@@ -675,7 +675,7 @@ describe('Image metadata', () => {
}); });
it('keep existing ICC profile, avoid colour transform', async () => { it('keep existing ICC profile, avoid colour transform', async () => {
const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile, { failOn: 'error' }) const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile)
.keepIccProfile() .keepIccProfile()
.raw() .raw()
.toBuffer(); .toBuffer();
@@ -721,7 +721,7 @@ describe('Image metadata', () => {
}); });
it('transform to invalid ICC profile emits warning', async () => { it('transform to invalid ICC profile emits warning', async () => {
const img = sharp({ create, failOn: 'error' }) const img = sharp({ create })
.png() .png()
.withIccProfile(fixtures.path('invalid-illuminant.icc')); .withIccProfile(fixtures.path('invalid-illuminant.icc'));