Reject non-finite PPI layer opacity

This commit is contained in:
2026-06-02 17:37:32 +02:00
parent 9759abde44
commit 52da64fc96
4 changed files with 50 additions and 2 deletions

View File

@@ -92,7 +92,7 @@ Known local toolchain state:
`pp_paint_renderer`, `pp_paint_renderer`,
`pp_ui_core`, `pano_cli`, and their current headless test binaries, `pp_ui_core`, `pano_cli`, and their current headless test binaries,
including foundation binary-stream/event/logging/task queue coverage, PNG metadata and including foundation binary-stream/event/logging/task queue coverage, PNG metadata and
decode, PPI header/layout, settings document, document decode, PPI header/layout/non-finite opacity rejection, settings document, document
snapshot/per-layer-frame/move/duration/face-pixel/PPI export coverage, snapshot/per-layer-frame/move/duration/face-pixel/PPI export coverage,
snapshot-embedded face-payload rejection, paint brush/final-blend/ snapshot-embedded face-payload rejection, paint brush/final-blend/
stroke-alpha-blend/stroke spacing/stroke stress/stroke-script coverage, stroke-alpha-blend/stroke spacing/stroke stress/stroke-script coverage,

View File

@@ -316,7 +316,7 @@ PNG IHDR metadata parsing, PPI header/project byte-layout/body-summary
recognition, layer/frame indexing, dirty-face PNG payload metadata validation, recognition, layer/frame indexing, dirty-face PNG payload metadata validation,
asset-level RGBA PNG payload decoding, and a pure typed settings document asset-level RGBA PNG payload decoding, and a pure typed settings document
model, with model, with
corrupt/truncated/unsupported, extreme-dimension, and key/value limit tests. corrupt/truncated/unsupported, non-finite opacity, extreme-dimension, and key/value limit tests.
`pp_paint` has started with pure brush parameter validation/stamp evaluation, `pp_paint` has started with pure brush parameter validation/stamp evaluation,
CPU reference math for the five current final RGBA shader blend modes plus the CPU reference math for the five current final RGBA shader blend modes plus the
shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic

View File

@@ -5,6 +5,7 @@
#include <array> #include <array>
#include <bit> #include <bit>
#include <cmath>
#include <limits> #include <limits>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -338,6 +339,11 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_impl(
seen_orders[order.value()] = true; seen_orders[order.value()] = true;
} }
if (!std::isfinite(opacity.value())) {
return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::invalid_argument("PPI layer opacity must be finite"));
}
if (opacity.value() < 0.0F || opacity.value() > 1.0F) { if (opacity.value() < 0.0F || opacity.value() > 1.0F) {
return pp::foundation::Result<PpiBodySummary>::failure( return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range")); pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range"));
@@ -677,6 +683,11 @@ pp::foundation::Result<std::vector<std::byte>> create_ppi_project(PpiProjectConf
pp::foundation::Status::out_of_range("PPI layer name exceeds the configured limit")); pp::foundation::Status::out_of_range("PPI layer name exceeds the configured limit"));
} }
if (!std::isfinite(layer.metadata.opacity)) {
return pp::foundation::Result<std::vector<std::byte>>::failure(
pp::foundation::Status::invalid_argument("PPI layer opacity must be finite"));
}
if (layer.metadata.opacity < 0.0F || layer.metadata.opacity > 1.0F) { if (layer.metadata.opacity < 0.0F || layer.metadata.opacity > 1.0F) {
return pp::foundation::Result<std::vector<std::byte>>::failure( return pp::foundation::Result<std::vector<std::byte>>::failure(
pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range")); pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range"));

View File

@@ -5,6 +5,7 @@
#include <bit> #include <bit>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -43,6 +44,19 @@ void append_f32(std::vector<std::byte>& bytes, float value)
append_u32(bytes, std::bit_cast<std::uint32_t>(value)); append_u32(bytes, std::bit_cast<std::uint32_t>(value));
} }
void write_u32_at(std::vector<std::byte>& bytes, std::size_t offset, std::uint32_t value)
{
bytes[offset + 0U] = static_cast<std::byte>(value & 0xffU);
bytes[offset + 1U] = static_cast<std::byte>((value >> 8U) & 0xffU);
bytes[offset + 2U] = static_cast<std::byte>((value >> 16U) & 0xffU);
bytes[offset + 3U] = static_cast<std::byte>((value >> 24U) & 0xffU);
}
void write_f32_at(std::vector<std::byte>& bytes, std::size_t offset, float value)
{
write_u32_at(bytes, offset, std::bit_cast<std::uint32_t>(value));
}
void append_ascii(std::vector<std::byte>& bytes, std::string_view value) void append_ascii(std::vector<std::byte>& bytes, std::string_view value)
{ {
for (const auto ch : value) { for (const auto ch : value) {
@@ -375,9 +389,16 @@ void rejects_invalid_project_body_summaries(pp::tests::Harness& h)
auto bad_layer_name = minimal_project(); auto bad_layer_name = minimal_project();
bad_layer_name[ppi_header_size + (128U * 128U * 4U) + 24U] = std::byte { 255 }; bad_layer_name[ppi_header_size + (128U * 128U * 4U) + 24U] = std::byte { 255 };
auto non_finite_opacity = minimal_project();
write_f32_at(
non_finite_opacity,
ppi_header_size + (128U * 128U * 4U) + 20U,
std::numeric_limits<float>::quiet_NaN());
const auto truncated_result = parse_ppi_project_summary(truncated); const auto truncated_result = parse_ppi_project_summary(truncated);
const auto mismatched_frames_result = parse_ppi_project_summary(mismatched_frames); const auto mismatched_frames_result = parse_ppi_project_summary(mismatched_frames);
const auto bad_layer_name_result = parse_ppi_project_summary(bad_layer_name); const auto bad_layer_name_result = parse_ppi_project_summary(bad_layer_name);
const auto non_finite_opacity_result = parse_ppi_project_summary(non_finite_opacity);
PP_EXPECT(h, !truncated_result.ok()); PP_EXPECT(h, !truncated_result.ok());
PP_EXPECT(h, truncated_result.status().code == StatusCode::out_of_range); PP_EXPECT(h, truncated_result.status().code == StatusCode::out_of_range);
@@ -385,6 +406,8 @@ void rejects_invalid_project_body_summaries(pp::tests::Harness& h)
PP_EXPECT(h, mismatched_frames_result.status().code == StatusCode::invalid_argument); PP_EXPECT(h, mismatched_frames_result.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_layer_name_result.ok()); PP_EXPECT(h, !bad_layer_name_result.ok());
PP_EXPECT(h, bad_layer_name_result.status().code == StatusCode::out_of_range); PP_EXPECT(h, bad_layer_name_result.status().code == StatusCode::out_of_range);
PP_EXPECT(h, !non_finite_opacity_result.ok());
PP_EXPECT(h, non_finite_opacity_result.status().code == StatusCode::invalid_argument);
} }
void creates_minimal_project_for_roundtrip_load(pp::tests::Harness& h) void creates_minimal_project_for_roundtrip_load(pp::tests::Harness& h)
@@ -743,6 +766,18 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
.frame_duration_ms = 100, .frame_duration_ms = 100,
.dirty_faces = {}, .dirty_faces = {},
}); });
const auto non_finite_opacity = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
.width = 128,
.height = 128,
.layer_name = "Ink",
.layer_metadata = pp::assets::PpiLayerMetadataConfig {
.opacity = std::numeric_limits<float>::quiet_NaN(),
},
.layer_count = 1,
.frame_count = 1,
.frame_duration_ms = 100,
.dirty_faces = {},
});
const auto bad_blend_mode = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig { const auto bad_blend_mode = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
.width = 128, .width = 128,
.height = 128, .height = 128,
@@ -798,6 +833,8 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
PP_EXPECT(h, no_layers.status().code == StatusCode::out_of_range); PP_EXPECT(h, no_layers.status().code == StatusCode::out_of_range);
PP_EXPECT(h, !bad_opacity.ok()); PP_EXPECT(h, !bad_opacity.ok());
PP_EXPECT(h, bad_opacity.status().code == StatusCode::out_of_range); PP_EXPECT(h, bad_opacity.status().code == StatusCode::out_of_range);
PP_EXPECT(h, !non_finite_opacity.ok());
PP_EXPECT(h, non_finite_opacity.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_blend_mode.ok()); PP_EXPECT(h, !bad_blend_mode.ok());
PP_EXPECT(h, bad_blend_mode.status().code == StatusCode::out_of_range); PP_EXPECT(h, bad_blend_mode.status().code == StatusCode::out_of_range);
PP_EXPECT(h, !duplicate_dirty_face.ok()); PP_EXPECT(h, !duplicate_dirty_face.ok());