blob: d773388df43875cd889ab3f139ced8ed415bce33 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include "cc/paint/paint_op_buffer.h"
#include "cc/test/paint_op_helper.h"
#include "cc/test/transfer_cache_test_helper.h"
#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
// paint_op_buffer_eq_fuzzer deserializes and reserializes paint ops to
// make sure that this does not modify or incorrectly serialize them.
// This is intended to be a fuzzing correctness test.
//
// Compare this to paint_op_buffer_fuzzer which makes sure that deserializing
// ops and rasterizing those ops is safe.
//
// This test performs the following operation:
//
// serialized1 -> deserialized1 -> serialized2 -> deserialized2 -> serialized3
//
// It does a binary comparison that serialized2 == serialized3
// Ideally this test would compare serialized1 to serialized2, however:
// (1) Deserializing is a destructive process on bad input, e.g. SkMatrix
// that says it is identity will be clobbered to have identity values.
// (2) Padding for alignment is skipped and so serialized1 may have garbage.
// serialized2 and serialized3 are cleared to zero first.
//
// Binary comparing serialized2 to serialized3 is not identical to comparing
// serialized1 to serialized2, as this could overlook some bugs that clobbered
// object state to something that serialized cleanly at that point.
// To mitigate those errors, this test also compares the logical equality
// deserialized1 and deserialized2 using PaintOp::operator==.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
const size_t kMaxSerializedSize = 1000000;
// TODO(enne): add an image provider here once deserializing supports that.
SkNoDrawCanvas canvas(100, 100);
cc::TransferCacheTestHelper transfer_cache_helper;
cc::PaintOp::SerializeOptions serialize_options;
serialize_options.transfer_cache = &transfer_cache_helper;
serialize_options.canvas = &canvas;
cc::PaintOp::DeserializeOptions deserialize_options;
deserialize_options.transfer_cache = &transfer_cache_helper;
// Need 4 bytes to be able to read the type/skip.
if (size < 4)
return 0;
size_t serialized_size = reinterpret_cast<const cc::PaintOp*>(data)->skip;
if (serialized_size > kMaxSerializedSize)
return 0;
// If the op has a skip that runs off the end of the input, then ignore.
if (serialized_size > size)
return 0;
std::unique_ptr<char, base::AlignedFreeDeleter> deserialized1(
static_cast<char*>(base::AlignedAlloc(sizeof(cc::LargestPaintOp),
cc::PaintOpBuffer::PaintOpAlign)));
size_t bytes_read1 = 0;
cc::PaintOp* deserialized_op1 = cc::PaintOp::Deserialize(
data, size, deserialized1.get(), sizeof(cc::LargestPaintOp), &bytes_read1,
deserialize_options);
// Failed to deserialize, so abort.
if (!deserialized_op1)
return 0;
// DrawTextBlob ops contain two pieces of information: a text blob, and a
// vector of typefaces that are used in the blob. However, during
// deserialization we never need to reconstruct the vector of typefaces, since
// it is only used to reconstruct the blob directly. This, however, poses a
// problem for this fuzzer since we will then try to serialize the op again,
// resulting in an assert. The assert says that we don't have typefaces
// (serialized from the vector) that are required to serialize the text blob.
// The solution is to skip the equality fuzzer for DrawTextBlob ops.
//
// Normally the initial deserialization would also fail and we would early out
// above, since the transfer cache doesn't have any entries to reconstruct the
// text blob. However, Skia, given some data, seems to be able to construct an
// SkTextBlob without accessing the given cataloger. See crbug.com/798939 for
// an example of such a test case.
if (deserialized_op1->GetType() == cc::PaintOpType::DrawTextBlob) {
deserialized_op1->DestroyThis();
return 0;
}
// If we get to this point, then the op should be ok to serialize/deserialize
// and any failure is a dcheck.
std::unique_ptr<char, base::AlignedFreeDeleter> serialized2(
static_cast<char*>(base::AlignedAlloc(serialized_size,
cc::PaintOpBuffer::PaintOpAlign)));
memset(serialized2.get(), 0, serialized_size);
size_t written_bytes2 = deserialized_op1->Serialize(
serialized2.get(), serialized_size, serialize_options);
CHECK_LE(written_bytes2, serialized_size);
std::unique_ptr<char, base::AlignedFreeDeleter> deserialized2(
static_cast<char*>(base::AlignedAlloc(sizeof(cc::LargestPaintOp),
cc::PaintOpBuffer::PaintOpAlign)));
size_t bytes_read2 = 0;
cc::PaintOp* deserialized_op2 = cc::PaintOp::Deserialize(
data, size, deserialized2.get(), sizeof(cc::LargestPaintOp), &bytes_read2,
deserialize_options);
CHECK(deserialized_op2);
CHECK_EQ(bytes_read1, bytes_read2);
std::unique_ptr<char, base::AlignedFreeDeleter> serialized3(
static_cast<char*>(
base::AlignedAlloc(written_bytes2, cc::PaintOpBuffer::PaintOpAlign)));
memset(serialized3.get(), 0, written_bytes2);
size_t written_bytes3 = deserialized_op2->Serialize(
serialized3.get(), written_bytes2, serialize_options);
CHECK_EQ(written_bytes2, written_bytes3);
CHECK(*deserialized_op1 == *deserialized_op2)
<< "\n1: " << cc::PaintOpHelper::ToString(deserialized_op1)
<< "\n2: " << cc::PaintOpHelper::ToString(deserialized_op2);
CHECK_EQ(0, memcmp(serialized2.get(), serialized3.get(), written_bytes2));
deserialized_op1->DestroyThis();
deserialized_op2->DestroyThis();
return 0;
}