blob: 3932ad9068bac1223e95e97f8a0985a9d01a9c16 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./fuzztest/internal/serialization.h"
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <map>
#include <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "./fuzztest/internal/test_protobuf.pb.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/message_differencer.h"
namespace fuzztest::internal {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::IsFalse;
using ::testing::NanSensitiveDoubleEq;
using ::testing::Not;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::Property;
using ::testing::StartsWith;
using ::testing::VariantWith;
auto HasNoValue() { return Property(&IRObject::HasValue, IsFalse()); }
template <typename T>
auto ValueIs(const T& v) {
if constexpr (std::is_same_v<T, double>) {
return Property(&IRObject::GetScalar<T>, Optional(NanSensitiveDoubleEq(v)));
} else {
return Property(&IRObject::GetScalar<T>, Optional(Eq(v)));
}
}
template <typename... T>
auto SubsAre(const T&... v) {
return Property(&IRObject::Subs, Optional(ElementsAre(v...)));
}
struct VerifyVisitor {
const IRObjectTestProto& proto;
void operator()(uint64_t v) const {
EXPECT_EQ(v, proto.i());
EXPECT_EQ(proto.sub_size(), 0);
}
void operator()(double v) const {
EXPECT_THAT(v, NanSensitiveDoubleEq(proto.d()));
EXPECT_EQ(proto.sub_size(), 0);
}
void operator()(const std::string& v) const {
EXPECT_EQ(v, proto.s());
EXPECT_EQ(proto.sub_size(), 0);
}
void operator()(const std::vector<IRObject>& subs) const {
EXPECT_EQ(0, proto.value_case());
ASSERT_EQ(subs.size(), proto.sub_size());
for (int i = 0; i < subs.size(); ++i) {
subs[i].visit(VerifyVisitor{proto.sub(i)});
}
}
void operator()(std::monostate) const {
EXPECT_EQ(0, proto.value_case());
EXPECT_EQ(proto.sub_size(), 0);
}
};
TEST(SerializerTest, HeaderIsCorrect) {
EXPECT_THAT(SerializeIRObject(IRObject{0}, /*binary_format=*/false),
AllOf(StartsWith("FUZZTESTv1"), Not(StartsWith("FUZZTESTv1b"))));
EXPECT_THAT(SerializeIRObject(IRObject{0}, /*binary_format=*/true),
StartsWith("FUZZTESTv1b"));
}
// We manually write the serialized form to test the error handling of the
// parser. The serializer would not generate these, so we can't use it.
TEST(SerializerTest, WrongHeaderWontParse) {
EXPECT_THAT(ParseIRObject("FUZZTESTv2"), Not(Optional(_)));
EXPECT_THAT(ParseIRObject("FUZZtESTv1"), Not(Optional(_)));
EXPECT_THAT(ParseIRObject("-FUZZTESTv1"), Not(Optional(_)));
}
TEST(SerializerTest, SubsAccesors) {
IRObject obj;
// The empty obj shows as an empty subs too due to how it is serialized.
EXPECT_THAT(obj.Subs(), Optional(ElementsAre()));
auto& subs = obj.MutableSubs();
EXPECT_THAT(obj.Subs(), Optional(ElementsAre()));
subs.emplace_back(17);
subs.emplace_back("ABC");
EXPECT_THAT(obj.Subs(), Optional(ElementsAre(ValueIs<uint64_t>(17),
ValueIs<std::string>("ABC"))));
// Another call keeps them.
obj.MutableSubs();
EXPECT_THAT(obj.Subs(), Optional(ElementsAre(ValueIs<uint64_t>(17),
ValueIs<std::string>("ABC"))));
}
IRObject CreateRecursiveObject(int depth) {
IRObject obj;
if (depth > 0) {
obj.MutableSubs().push_back(CreateRecursiveObject(depth - 1));
}
return obj;
}
TEST(SerializerTest, RecursiveStructureBelowDepthLimitGetsParsed) {
EXPECT_TRUE(
ParseIRObject(SerializeIRObject(CreateRecursiveObject(/*depth=*/100),
/*binary_format=*/false))
.has_value());
EXPECT_TRUE(
ParseIRObject(SerializeIRObject(CreateRecursiveObject(/*depth=*/100),
/*binary_format=*/true))
.has_value());
}
TEST(SerializerTest, RecursiveStructureAboveDepthLimitDoesNotGetParsed) {
EXPECT_FALSE(
ParseIRObject(SerializeIRObject(CreateRecursiveObject(/*depth=*/150),
/*binary_format=*/false))
.has_value());
EXPECT_FALSE(
ParseIRObject(SerializeIRObject(CreateRecursiveObject(/*depth=*/150),
/*binary_format=*/true))
.has_value());
}
struct SerializerRoundTripParam {
bool binary_format;
};
class SerializerRoundTripTest
: public testing::TestWithParam<SerializerRoundTripParam> {};
void VerifyProtobufFormat(const IRObject& object) {
IRObjectTestProto proto;
std::string s = SerializeIRObject(object, /*binary_format=*/false);
// Chop the header.
s.erase(0, strlen("FUZZTESTv1\n"));
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(s, &proto));
object.visit(VerifyVisitor{proto});
}
template <typename... T>
void RoundTripVerify(bool binary_format, const T&... values) {
IRObject object;
object.MutableSubs() = std::vector{IRObject{values}...};
std::string s = SerializeIRObject(object, binary_format);
SCOPED_TRACE(s);
if (!binary_format) VerifyProtobufFormat(object);
EXPECT_THAT(ParseIRObject(s), Optional(SubsAre(ValueIs<T>(values)...)));
}
template <typename T>
using L = std::numeric_limits<T>;
TEST_P(SerializerRoundTripTest, ScalarsRoundTrip) {
using S = std::string;
RoundTripVerify(GetParam().binary_format, uint64_t{0}, L<uint64_t>::min(),
L<uint64_t>::max(), //
double{1.2}, L<double>::max(), L<double>::min(), //
L<double>::lowest(), L<double>::infinity(), std::nan(""), //
S(""), S("A"), S("\nSpecial\r Chars\"\n12\\"),
S("\0Zero\0", 6));
}
TEST_P(SerializerRoundTripTest, SubobjectsRoundTrip) {
IRObject root{std::vector{
IRObject{"child1"}, IRObject{"child2"},
IRObject{std::vector<IRObject>{
IRObject{"child3.1"}, IRObject{std::vector{IRObject{"child3.2.1"},
IRObject{"child3.2.2"}}}}},
IRObject{"child4"}}};
std::string s = SerializeIRObject(root, GetParam().binary_format);
SCOPED_TRACE(s);
if (!GetParam().binary_format) VerifyProtobufFormat(root);
std::optional<IRObject> obj = ParseIRObject(s);
EXPECT_THAT(
obj, Optional(SubsAre(
ValueIs<std::string>("child1"), ValueIs<std::string>("child2"),
SubsAre(ValueIs<std::string>("child3.1"),
SubsAre(ValueIs<std::string>("child3.2.1"),
ValueIs<std::string>("child3.2.2"))),
ValueIs<std::string>("child4"))));
}
TEST_P(SerializerRoundTripTest, EmptyObjectRoundTrips) {
std::string s = SerializeIRObject(IRObject{}, GetParam().binary_format);
SCOPED_TRACE(s);
EXPECT_THAT(ParseIRObject(s), Optional(HasNoValue()));
}
template <typename T>
void TestScalarRoundTrips(bool binary_format, T value) {
EXPECT_THAT(IRObject(value).GetScalar<T>(), Optional(value));
IRObject obj;
obj.SetScalar(value);
EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
auto roundtrip = ParseIRObject(SerializeIRObject(obj, binary_format));
EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
}
TEST_P(SerializerRoundTripTest, ScalarConversionsWorks) {
TestScalarRoundTrips(GetParam().binary_format, true);
TestScalarRoundTrips(GetParam().binary_format, 'a');
TestScalarRoundTrips(GetParam().binary_format, -1);
TestScalarRoundTrips(GetParam().binary_format, size_t{123});
TestScalarRoundTrips(GetParam().binary_format, int64_t{-1});
TestScalarRoundTrips(GetParam().binary_format, -123LL);
TestScalarRoundTrips(GetParam().binary_format, 1.5f);
TestScalarRoundTrips(GetParam().binary_format, std::string("ABC"));
enum E { kEnum = 18 };
enum class E2 { kEnum = 18 };
TestScalarRoundTrips(GetParam().binary_format, E::kEnum);
TestScalarRoundTrips(GetParam().binary_format, E2::kEnum);
}
INSTANTIATE_TEST_SUITE_P(
SerializerRoundTripTest, SerializerRoundTripTest,
testing::Values(SerializerRoundTripParam{/*binary_format=*/false},
SerializerRoundTripParam{/*binary_format=*/true}));
TEST(TextFormatSerializerTest, IndentationIsCorrect) {
// This test checks the actual returned string to verify the indentation.
// The indentation is irrelevant for the correctness of the algorithm, but it
// is good for human readability.
IRObject root{
std::vector{IRObject{uint64_t{1}}, IRObject{uint64_t{2}},
IRObject{std::vector<IRObject>{
IRObject{uint64_t{31}},
IRObject{std::vector{IRObject{uint64_t{321}},
IRObject{uint64_t{322}}}}}},
IRObject{uint64_t{4}}}};
std::string s = SerializeIRObject(root, /*binary_format=*/false);
EXPECT_EQ(s, R"(FUZZTESTv1
sub { i: 1 }
sub { i: 2 }
sub {
sub { i: 31 }
sub {
sub { i: 321 }
sub { i: 322 }
}
}
sub { i: 4 }
)") << s;
}
TEST(TextFormatSerializerTest, HandlesUnterminatedString) {
EXPECT_THAT(ParseIRObject("FUZZTESTv1\""), Not(Optional(_)));
}
TEST(TextFormatSerializerTest, BadScalarWontParse) {
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i: 1"), Optional(ValueIs<uint64_t>(1)));
// Out of bounds values
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i: 123456789012345678901"),
Not(Optional(_)));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i: -1"), Not(Optional(_)));
// Missing :
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i 1"), Not(Optional(_)));
// Bad tag
EXPECT_THAT(ParseIRObject("FUZZTESTv1 x: 1"), Not(Optional(_)));
// Wrong separator
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i; 1"), Not(Optional(_)));
// Extra close
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i: 1}"), Not(Optional(_)));
}
TEST(TextFormatSerializerTest, BadSubWontParse) {
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub { i: 0 }"),
Optional(SubsAre(ValueIs<uint64_t>(0))));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub: { }"), Not(Optional(_)));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub }"), Not(Optional(_)));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub { "), Not(Optional(_)));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub { } }"), Not(Optional(_)));
}
TEST(TextFormatSerializerTest, ExtraWhitespaceIsFine) {
EXPECT_THAT(ParseIRObject("FUZZTESTv1 i: 0 \n "),
Optional(ValueIs<uint64_t>(0)));
EXPECT_THAT(ParseIRObject("FUZZTESTv1 sub { \n i: 0 \n} \n "),
Optional(SubsAre(ValueIs<uint64_t>(0))));
}
TEST(BinaryFormatSerializerTest, MalformedObjectSizeIsRejectedWithoutOOM) {
static constexpr char kGoodInput[] =
"FUZZTESTv1b\x04\x01\x00\x00\x00\x00\x00\x00\x00\x00";
static constexpr char kBadInput[] =
"FUZZTESTv1b\x04\xff\xff\xff\xff\xff\xff\xff\xff\x00";
EXPECT_THAT(ParseIRObject({kGoodInput, sizeof(kGoodInput) - 1}),
Optional(SubsAre(HasNoValue())));
// Expect grace failure instead of OOM error.
EXPECT_EQ(ParseIRObject({kBadInput, sizeof(kBadInput) - 1}), std::nullopt);
}
TEST(IRToCorpus, SpecializationIsBackwardCompatible) {
EXPECT_THAT(
(IRObject{std::vector<IRObject>{IRObject{1}, IRObject{2}, IRObject{3}}}
.ToCorpus<std::vector<int8_t>>()),
Optional(ElementsAre(1, 2, 3)));
}
TEST(CorpusToIR, Specialization) {
EXPECT_TRUE(IRObject::FromCorpus(std::vector<int8_t>{1, 2, 3})
.GetScalar<std::string>()
.has_value());
EXPECT_TRUE(IRObject::FromCorpus(std::vector<uint8_t>{1, 2, 3})
.GetScalar<std::string>()
.has_value());
EXPECT_TRUE(
IRObject::FromCorpus(
std::vector<std::byte>{std::byte{1}, std::byte{2}, std::byte{3}})
.GetScalar<std::string>()
.has_value());
}
TEST(CorpusToIR, ValidRoundTrips) {
const auto round_trip = [](auto v) -> std::optional<decltype(v)> {
return IRObject::FromCorpus(v).template ToCorpus<decltype(v)>();
};
// Monostates
EXPECT_THAT(round_trip(std::true_type{}), Optional(std::true_type{}));
EXPECT_THAT(round_trip(std::false_type{}), Optional(std::false_type{}));
// Scalars
EXPECT_THAT(round_trip('a'), Optional('a'));
EXPECT_THAT(round_trip(true), Optional(true));
EXPECT_THAT(round_trip(false), Optional(false));
EXPECT_THAT(round_trip(-1), Optional(-1));
EXPECT_THAT(round_trip(size_t{123}), Optional(size_t{123}));
EXPECT_THAT(round_trip(1.5f), Optional(1.5f));
EXPECT_THAT(round_trip(1234.), Optional(1234.));
EXPECT_THAT(round_trip(std::string("ABC")), Optional(std::string("ABC")));
enum E { kEnum };
enum class E2 { kEnum };
EXPECT_THAT(round_trip(E::kEnum), Optional(E::kEnum));
EXPECT_THAT(round_trip(E2::kEnum), Optional(E2::kEnum));
// Specializations
EXPECT_THAT(round_trip(std::vector<int8_t>{1, 2, 3}),
Optional(ElementsAre(1, 2, 3)));
EXPECT_THAT(round_trip(std::vector<uint8_t>{1, 2, 3}),
Optional(ElementsAre(1, 2, 3)));
EXPECT_THAT(round_trip(std::vector<std::byte>{std::byte{1}, std::byte{2},
std::byte{3}}),
Optional(ElementsAre(std::byte{1}, std::byte{2}, std::byte{3})));
// Compound types
EXPECT_THAT(round_trip(std::vector<bool>{true, false, true, true}),
Optional(ElementsAre(true, false, true, true)));
EXPECT_THAT(round_trip(std::vector{1, 2, 3}), Optional(ElementsAre(1, 2, 3)));
EXPECT_THAT(round_trip(std::vector{std::string("A"), std::string("B")}),
Optional(ElementsAre("A", "B")));
EXPECT_THAT(round_trip(std::tuple(1, std::string("A"), 1.4)),
Optional(FieldsAre(1, "A", 1.4)));
EXPECT_THAT(round_trip(std::vector{std::tuple(1, 2)}),
Optional(ElementsAre(FieldsAre(1, 2))));
EXPECT_THAT(round_trip(std::array<int, 3>{0, 2, 4}),
Optional(ElementsAre(0, 2, 4)));
EXPECT_THAT(round_trip(std::variant<int, std::string>(1000)),
Optional(VariantWith<int>(1000)));
EXPECT_THAT(round_trip(std::variant<int, std::string>("ABC")),
Optional(VariantWith<std::string>("ABC")));
EXPECT_THAT(round_trip(std::map<int, int>{{1, 2}, {3, 4}}),
Optional(ElementsAre(Pair(1, 2), Pair(3, 4))));
// Proto
TestProtobuf proto;
proto.set_b(true);
const std::optional<TestProtobuf> round_trip_proto = round_trip(proto);
EXPECT_TRUE(
round_trip_proto.has_value() &&
google::protobuf::util::MessageDifferencer::Equals(*round_trip_proto, proto));
// IRObject's identity
IRObject obj(1979);
obj = round_trip(obj).value();
EXPECT_THAT(obj.GetScalar<int>(), Optional(1979));
obj.MutableSubs().emplace_back("ABC");
obj = round_trip(obj).value();
EXPECT_THAT(obj.Subs(), Optional(ElementsAre(ValueIs<std::string>("ABC"))));
}
TEST(CorpusToIR, FailureConditions) {
// simple
EXPECT_FALSE(IRObject(1).ToCorpus<std::true_type>());
EXPECT_FALSE(IRObject("ABC").ToCorpus<int>());
// variant
{
using V = std::variant<int, std::string>;
// Valid index, but bad value.
IRObject var = IRObject::FromCorpus(V(2));
auto& v = var.MutableSubs();
EXPECT_THAT(v[0].GetScalar<int>(), Optional(0));
v[0] = IRObject(1);
EXPECT_FALSE(var.ToCorpus<V>());
// Reset
v[0] = IRObject(0);
EXPECT_THAT(var.ToCorpus<V>(), Optional(VariantWith<int>(2)));
// Invalid index
v[0] = IRObject(2);
EXPECT_FALSE(var.ToCorpus<V>());
}
// proto
EXPECT_FALSE(IRObject(1).ToCorpus<TestProtobuf>());
EXPECT_TRUE(IRObject("").ToCorpus<TestProtobuf>());
// container
using Vector = std::vector<int>;
EXPECT_FALSE(IRObject(1).ToCorpus<Vector>());
EXPECT_TRUE(IRObject(std::vector<IRObject>{IRObject(1), IRObject(2)})
.ToCorpus<Vector>());
EXPECT_FALSE(IRObject(std::vector<IRObject>{IRObject(1), IRObject("ABC")})
.ToCorpus<Vector>());
// tuples
using Tuple = std::tuple<int, std::string>;
EXPECT_FALSE(IRObject(1).ToCorpus<Tuple>());
EXPECT_TRUE(IRObject(std::vector<IRObject>{IRObject(1), IRObject("ABC")})
.ToCorpus<Tuple>());
EXPECT_FALSE(IRObject(std::vector<IRObject>{IRObject("A"), IRObject("ABC")})
.ToCorpus<Tuple>());
}
// TODO(sbenzaquen): Add tests for failing conditions in the IR->Corpus conversion.
} // namespace
} // namespace fuzztest::internal