script: Fix panic in compressedTexSubImage2D when no base image exists (#44050)

Calling `compressedTexSubImage2D` without a prior `compressedTexImage2D`
for the same texture target and level caused the panic at
`tex_image_2d.rs:633` because `image_info_for_target` returned `None`
and `.unwrap()` was called on it.

Per the WebGL spec, this should generate `GL_INVALID_OPERATION` instead
of panicking. So this PR replaces the unwrap with proper error handling.
And also includes a WPT crash test that reproduces the original panic.

Fixes #43527

---------

Signed-off-by: thebabalola <t.babalolajoseph@gmail.com>
This commit is contained in:
Babalola Taiwo J
2026-04-10 18:08:55 +01:00
committed by GitHub
parent daf15e8e07
commit 3bf38ef7ea
3 changed files with 65 additions and 8 deletions

View File

@@ -53,6 +53,8 @@ pub(crate) enum TexImageValidationError {
InvalidCompressionFormat,
/// Invalid X/Y texture offset parameters.
InvalidOffsets,
/// No base image has been defined for this texture target and level.
MissingBaseTexture,
}
impl std::error::Error for TexImageValidationError {}
@@ -80,6 +82,7 @@ impl fmt::Display for TexImageValidationError {
NonPotTexture => "Expected a power of two texture",
InvalidCompressionFormat => "Unrecognized texture compression format",
InvalidOffsets => "Invalid X/Y texture offset parameters",
MissingBaseTexture => "No base image defined for this texture target and level",
};
write!(f, "TexImageValidationError({})", description)
}
@@ -630,7 +633,12 @@ impl WebGLValidator for CompressedTexSubImage2DValidator<'_> {
compression,
} = self.compression_validator.validate()?;
let tex_info = texture.image_info_for_target(&target, level).unwrap();
// GL_INVALID_OPERATION is generated if no base image has been defined
// for this texture target and level via compressedTexImage2D.
let Some(tex_info) = texture.image_info_for_target(&target, level) else {
context.webgl_error(InvalidOperation);
return Err(TexImageValidationError::MissingBaseTexture);
};
// GL_INVALID_VALUE is generated if:
// - xoffset or yoffset is less than 0

View File

@@ -244603,7 +244603,7 @@
"before-after-pseudo-element-scrolling.html": [
"805c01af85b7e01f73bda4a1ee90db50fa83ce60",
[
"css/css-overflow/before-after-pseudo-element-scrolling.html",
null,
[
[
"/css/css-overflow/reference/before-after-pseudo-element-scrolling-ref.html",
@@ -295379,7 +295379,7 @@
"word-break-keep-all-011.html": [
"c1d7af2496894ef609115d3b4fc01cb54ffb32a7",
[
"css/css-text/word-break/word-break-keep-all-011.html",
null,
[
[
"/css/css-text/word-break/reference/word-break-keep-all-011-ref.html",
@@ -378843,7 +378843,7 @@
"caret-visibility-after-creation-in-script.html": [
"6e9e8fd1618fd9fd4bb90a1bc2a8199c2e447764",
[
"html/rendering/replaced-elements/the-textarea-element/caret-visibility-after-creation-in-script.html",
null,
[
[
"/html/rendering/replaced-elements/the-textarea-element/caret-visibility-after-creation-in-script-ref.html",
@@ -563188,10 +563188,6 @@
"a5e37dce95b00834aa3d3b58008762f5801e9e5d",
[]
],
"browser.runtime.extension.js": [
"c41a1b795ea829493364767e9ea3403f0586c1bc",
[]
],
"resources": {
"runtime": {
"background.js": [
@@ -902757,6 +902753,15 @@
]
}
},
"web-extensions": {
"browser.runtime.extension.js": [
"c41a1b795ea829493364767e9ea3403f0586c1bc",
[
"web-extensions/browser.runtime.extension.html",
{}
]
]
},
"web-locks": {
"acquire.https.any.js": [
"54ae6f30e7add6f45c6b5ba7161999b5a6078871",
@@ -915672,6 +915677,13 @@
{}
]
],
"compressed-tex-sub-image-2d-without-base-image.html": [
"4627a2b4ba3c6759d5bd1194c6a86c97b466165f",
[
null,
{}
]
],
"compressedTexImage2D.html": [
"a974c65002448c0df1beb97f93d5cd1476f254ca",
[

View File

@@ -0,0 +1,37 @@
<!doctype html>
<title>compressedTexSubImage2D without prior compressedTexImage2D generates INVALID_OPERATION</title>
<link rel=author title="Babalola Taiwo Joseph" href=mailto:t.babalolajoseph@gmail.com>
<link rel=help href=https://www.khronos.org/registry/webgl/specs/latest/#5.14.8>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var canvas = document.createElement("canvas");
var gl = canvas.getContext("webgl");
assert_true(!!gl, "WebGL context should be available");
var s3tc = gl.getExtension("WEBGL_compressed_texture_s3tc");
var etc1 = gl.getExtension("WEBGL_compressed_texture_etc1");
var format, dataSize;
if (s3tc) {
format = s3tc.COMPRESSED_RGB_S3TC_DXT1_EXT;
dataSize = 8;
} else if (etc1) {
format = etc1.COMPRESSED_RGB_ETC1_WEBGL;
dataSize = 8;
}
assert_true(!!format, "A supported compressed texture format must be available");
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 4, 4, format, new Uint8Array(dataSize));
assert_equals(gl.getError(), gl.INVALID_OPERATION,
"compressedTexSubImage2D without a prior compressedTexImage2D should generate INVALID_OPERATION");
}, "compressedTexSubImage2D without base image generates INVALID_OPERATION");
</script>