blob: 773228c6828dedbf577e239c3b3660b1852885d9 [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 "google/protobuf/text_format.h"
#include "google/protobuf/util/message_differencer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "./fuzztest/internal/test_protobuf.pb.h"
namespace fuzztest::internal {
namespace {
using testing::_;
using testing::ElementsAre;
using testing::FieldsAre;
using testing::HasSubstr;
using testing::NanSensitiveDoubleEq;
using testing::Not;
using testing::Optional;
using testing::Pair;
using testing::VariantWith;
template <typename T>
auto ValueIs(const T& v) {
if constexpr (std::is_same_v<T, double>) {
return FieldsAre(VariantWith<double>(NanSensitiveDoubleEq(v)));
} else {
return FieldsAre(VariantWith<T>(v));
}
}
template <typename... T>
auto SubsAre(const T&... v) {
return FieldsAre(VariantWith<std::vector<IRObject>>(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) {
std::visit(VerifyVisitor{proto.sub(i)}, subs[i].value);
}
}
void operator()(std::monostate) const {
EXPECT_EQ(0, proto.value_case());
EXPECT_EQ(proto.sub_size(), 0);
}
};
void VerifyProtobufFormat(const IRObject& object) {
IRObjectTestProto proto;
std::string s = object.ToString<IRObject>();
// TODO(cnie0017) This is not a IRObjectTestProto
// since it is already being parsed to String once. It is now of IRObject form
// Chop the header.
s.erase(0, strlen("FUZZTESTv1\n"));
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(s, &proto));
std::visit(VerifyVisitor{proto}, object.value);
}
template <typename... T>
void RoundTripVerify(const T&... values) {
IRObject object;
object.value = std::vector{IRObject{values}...};
std::string s = object.ToString<IRObject>();
SCOPED_TRACE(s);
VerifyProtobufFormat(object);
// TODO(cnie0017): why can't use T?
EXPECT_THAT(IRObject{}.FromString<IRObject>(s),
Optional(SubsAre(ValueIs<T>(values)...)));
// EXPECT_THAT(IRObject::FromString<T>(s),
// Optional(SubsAre(ValueIs<T>(values)...)));
// // Initializer contains unexpanded parameter pack
// // 'T'clang(unexpanded_parameter_pack)
}
template <typename T>
using L = std::numeric_limits<T>;
TEST(SerializerTest, ScalarsRoundTrip) {
using S = std::string;
RoundTripVerify(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(SerializerTest, 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 = root.ToString<IRObject>();
SCOPED_TRACE(s);
VerifyProtobufFormat(root);
std::optional<IRObject> obj = IRObject{}.FromString<IRObject>(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(SerializerTest, EmptyObjectRoundTrips) {
std::string s = IRObject{}.ToString<std::nullptr_t>();
SCOPED_TRACE(s);
EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>(s),
Optional(ValueIs<std::monostate>({})));
}
TEST(SerializerTest, 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 = root.ToString<IRObject>();
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 }
)");
}
// TODO(cnie0017): ensure the type passed in are correct
// 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(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 0"),
Optional(_));
EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZTESTv2"),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZtESTv1"),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("-FUZZTESTv1"),
Not(Optional(_)));
}
TEST(SerializerTest, HandlesUnterminatedString) {
EXPECT_THAT(IRObject{}.FromString<std::nullptr_t>("FUZZTESTv1\""),
Not(Optional(_)));
}
TEST(SerializerTest, BadScalarWontParse) {
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 1"),
Optional(ValueIs<uint64_t>(1)));
// Out of bounds values
EXPECT_THAT(IRObject{}.FromString<std::int64_t>(
"FUZZTESTv1 i: 123456789012345678901"),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: -1"),
Not(Optional(_)));
// Missing :
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i 1"),
Not(Optional(_)));
// Bad tag
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 x: 1"),
Not(Optional(_)));
// Wrong separator
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i; 1"),
Not(Optional(_)));
// Extra close
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 1}"),
Not(Optional(_)));
}
TEST(SerializerTest, BadSubWontParse) {
EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { i: 0 }"),
Optional(SubsAre(ValueIs<uint64_t>(0))));
EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub: { }"),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub }"),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { "),
Not(Optional(_)));
EXPECT_THAT(IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { } }"),
Not(Optional(_)));
}
TEST(SerializerTest, ExtraWhitespaceIsFine) {
EXPECT_THAT(IRObject{}.FromString<std::int64_t>("FUZZTESTv1 i: 0 \n "),
Optional(ValueIs<uint64_t>(0)));
EXPECT_THAT(
IRObject{}.FromString<IRObject>("FUZZTESTv1 sub { \n i: 0 \n} \n "),
Optional(SubsAre(ValueIs<uint64_t>(0))));
}
template <typename T>
void TestScalarRoundTrips(T value) {
EXPECT_THAT(IRObject(value).GetScalar<T>(), Optional(value));
IRObject obj;
obj.SetScalar(value);
EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
auto roundtrip = IRObject{}.FromString<IRObject>(obj.ToString<IRObject>());
EXPECT_THAT(obj.GetScalar<T>(), Optional(value));
}
TEST(SerializerTest, ScalarConversionsWorks) {
TestScalarRoundTrips(true);
TestScalarRoundTrips('a');
TestScalarRoundTrips(-1);
TestScalarRoundTrips(size_t{123});
TestScalarRoundTrips(int64_t{-1});
TestScalarRoundTrips(-123LL);
TestScalarRoundTrips(1.5f);
TestScalarRoundTrips(std::string("ABC"));
enum E { kEnum = 18 };
enum class E2 { kEnum = 18 };
TestScalarRoundTrips(E::kEnum);
TestScalarRoundTrips(E2::kEnum);
}
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"))));
}
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));
// 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(FieldsAre(VariantWith<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 = std::get<std::vector<IRObject>>(var.value);
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>());
}
TEST(SerializerTest, SingleStringValueIsSerializedAsRawString) {
std::string s("ABC");
IRObject obj;
obj.value = s;
std::string s_serialized = obj.ToString<std::string>();
// Should have no header.
EXPECT_THAT(s_serialized, Not(HasSubstr("FUZZTESTv1\n")));
EXPECT_EQ(s, s_serialized);
}
TEST(SerializerTest, SingleProtoValueIsSerializaedAsRawString) {
IRObjectTestProto proto;
IRObject obj = IRObject::FromCorpus(proto);
std::string proto_serialized = obj.ToString<IRObjectTestProto>();
// Should have no header.
EXPECT_THAT(proto_serialized, Not(HasSubstr("FUZZTESTv1\n")));
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_serialized, &proto));
std::visit(VerifyVisitor{proto}, obj.value);
}
TEST(SerializerTest, SingleStringSerializationRoundTripVerify) {
std::string s("ABC");
IRObject obj;
obj.value = s;
std::string s_serialized = obj.ToString<std::string>();
std::optional<IRObject> obj_from_str =
IRObject{}.FromString<std::string>(s_serialized);
EXPECT_THAT(obj_from_str, Optional(ValueIs<std::string>(s)));
}
TEST(SerializerTest, SingleProtoSerializationRoundTripVerify) {
IRObjectTestProto proto;
std::string proto_str;
ASSERT_TRUE(google::protobuf::TextFormat::PrintToString(proto, &proto_str));
IRObject obj = IRObject::FromCorpus(proto);
std::string proto_serialized = obj.ToString<IRObjectTestProto>();
std::optional<IRObject> obj_from_str =
IRObject{}.FromString<std::string>(proto_serialized);
EXPECT_THAT(obj_from_str, Optional(ValueIs<std::string>(proto_str)));
}
// TODO(sbenzaquen): Add tests for failing conditions in the IR->Corpus conversion.
} // namespace
} // namespace fuzztest::internal