| // Copyright 2021 gRPC authors. |
| // |
| // 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 <functional> |
| #include <map> |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/types/optional.h" |
| |
| #include <grpc/support/log.h> |
| |
| #include "src/core/lib/promise/activity.h" |
| #include "src/core/lib/promise/detail/basic_join.h" |
| #include "src/core/lib/promise/join.h" |
| #include "src/core/lib/promise/map.h" |
| #include "src/core/lib/promise/poll.h" |
| #include "src/core/lib/promise/promise.h" |
| #include "src/core/lib/promise/race.h" |
| #include "src/core/lib/promise/seq.h" |
| #include "src/libfuzzer/libfuzzer_macro.h" |
| #include "test/core/promise/promise_fuzzer.pb.h" |
| |
| bool squelch = true; |
| bool leak_check = true; |
| |
| namespace grpc_core { |
| // Return type for infallible promises. |
| // We choose this so that it's easy to construct, and will trigger asan failures |
| // if misused, and is copyable. |
| using IntHdl = std::shared_ptr<int>; |
| |
| template <typename T> |
| using PromiseFactory = std::function<Promise<T>(T)>; |
| |
| namespace { |
| class Fuzzer { |
| public: |
| void Run(const promise_fuzzer::Msg& msg) { |
| // If there's no promise we can't construct and activity and... we're done. |
| if (!msg.has_promise()) { |
| return; |
| } |
| // Construct activity. |
| activity_ = MakeActivity( |
| [msg, this] { |
| return Seq(MakePromise(msg.promise()), |
| [] { return absl::OkStatus(); }); |
| }, |
| Scheduler{this}, |
| [this](absl::Status status) { |
| // Must only be called once |
| GPR_ASSERT(!done_); |
| // If we became certain of the eventual status, verify it. |
| if (expected_status_.has_value()) { |
| GPR_ASSERT(status == *expected_status_); |
| } |
| // Mark ourselves done. |
| done_ = true; |
| }); |
| for (int i = 0; !done_ && activity_ != nullptr && i < msg.actions_size(); |
| i++) { |
| // Do some things |
| const auto& action = msg.actions(i); |
| switch (action.action_type_case()) { |
| // Force a wakeup |
| case promise_fuzzer::Action::kForceWakeup: |
| activity_->ForceWakeup(); |
| break; |
| // Cancel from the outside |
| case promise_fuzzer::Action::kCancel: |
| ExpectCancelled(); |
| activity_.reset(); |
| break; |
| // Flush any pending wakeups |
| case promise_fuzzer::Action::kFlushWakeup: |
| if (wakeup_ != nullptr) std::exchange(wakeup_, nullptr)(); |
| break; |
| // Drop some wakeups (external system closed?) |
| case promise_fuzzer::Action::kDropWaker: { |
| int n = action.drop_waker(); |
| auto v = std::move(wakers_[n]); |
| wakers_.erase(n); |
| break; |
| } |
| // Wakeup some wakeups |
| case promise_fuzzer::Action::kAwakeWaker: { |
| int n = action.awake_waker(); |
| auto v = std::move(wakers_[n]); |
| wakers_.erase(n); |
| for (auto& w : v) { |
| w.Wakeup(); |
| } |
| break; |
| } |
| case promise_fuzzer::Action::ACTION_TYPE_NOT_SET: |
| break; |
| } |
| } |
| ExpectCancelled(); |
| activity_.reset(); |
| if (wakeup_ != nullptr) std::exchange(wakeup_, nullptr)(); |
| GPR_ASSERT(done_); |
| } |
| |
| private: |
| // Schedule wakeups against the fuzzer |
| struct Scheduler { |
| Fuzzer* fuzzer; |
| template <typename ActivityType> |
| class BoundScheduler { |
| public: |
| explicit BoundScheduler(Scheduler scheduler) |
| : fuzzer_(scheduler.fuzzer) {} |
| void ScheduleWakeup() { |
| GPR_ASSERT(static_cast<ActivityType*>(this) == |
| fuzzer_->activity_.get()); |
| GPR_ASSERT(fuzzer_->wakeup_ == nullptr); |
| fuzzer_->wakeup_ = [this]() { |
| static_cast<ActivityType*>(this)->RunScheduledWakeup(); |
| }; |
| } |
| |
| private: |
| Fuzzer* fuzzer_; |
| }; |
| }; |
| |
| // We know that if not already finished, the status when finished will be |
| // cancelled. |
| void ExpectCancelled() { |
| if (!done_ && !expected_status_.has_value()) { |
| expected_status_ = absl::CancelledError(); |
| } |
| } |
| |
| // Construct a promise factory from a protobuf |
| PromiseFactory<IntHdl> MakePromiseFactory( |
| const promise_fuzzer::PromiseFactory& p) { |
| switch (p.promise_factory_type_case()) { |
| case promise_fuzzer::PromiseFactory::kPromise: |
| return [p, this](IntHdl) { return MakePromise(p.promise()); }; |
| case promise_fuzzer::PromiseFactory::kLast: |
| return [](IntHdl h) { return [h]() { return h; }; }; |
| case promise_fuzzer::PromiseFactory::PROMISE_FACTORY_TYPE_NOT_SET: |
| break; |
| } |
| return [](IntHdl) { |
| return []() -> Poll<IntHdl> { return std::make_shared<int>(42); }; |
| }; |
| } |
| |
| // Construct a promise from a protobuf |
| Promise<IntHdl> MakePromise(const promise_fuzzer::Promise& p) { |
| switch (p.promise_type_case()) { |
| case promise_fuzzer::Promise::kSeq: |
| switch (p.seq().promise_factories_size()) { |
| case 1: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0))); |
| case 2: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0)), |
| MakePromiseFactory(p.seq().promise_factories(1))); |
| case 3: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0)), |
| MakePromiseFactory(p.seq().promise_factories(1)), |
| MakePromiseFactory(p.seq().promise_factories(2))); |
| case 4: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0)), |
| MakePromiseFactory(p.seq().promise_factories(1)), |
| MakePromiseFactory(p.seq().promise_factories(2)), |
| MakePromiseFactory(p.seq().promise_factories(3))); |
| case 5: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0)), |
| MakePromiseFactory(p.seq().promise_factories(1)), |
| MakePromiseFactory(p.seq().promise_factories(2)), |
| MakePromiseFactory(p.seq().promise_factories(3)), |
| MakePromiseFactory(p.seq().promise_factories(4))); |
| case 6: |
| return Seq(MakePromise(p.seq().first()), |
| MakePromiseFactory(p.seq().promise_factories(0)), |
| MakePromiseFactory(p.seq().promise_factories(1)), |
| MakePromiseFactory(p.seq().promise_factories(2)), |
| MakePromiseFactory(p.seq().promise_factories(3)), |
| MakePromiseFactory(p.seq().promise_factories(4)), |
| MakePromiseFactory(p.seq().promise_factories(5))); |
| } |
| break; |
| case promise_fuzzer::Promise::kJoin: |
| switch (p.join().promises_size()) { |
| case 1: |
| return Map(Join(MakePromise(p.join().promises(0))), |
| [](std::tuple<IntHdl> t) { return std::get<0>(t); }); |
| case 2: |
| return Map( |
| Join(MakePromise(p.join().promises(0)), |
| MakePromise(p.join().promises(1))), |
| [](std::tuple<IntHdl, IntHdl> t) { return std::get<0>(t); }); |
| case 3: |
| return Map(Join(MakePromise(p.join().promises(0)), |
| MakePromise(p.join().promises(1)), |
| MakePromise(p.join().promises(2))), |
| [](std::tuple<IntHdl, IntHdl, IntHdl> t) { |
| return std::get<0>(t); |
| }); |
| case 4: |
| return Map(Join(MakePromise(p.join().promises(0)), |
| MakePromise(p.join().promises(1)), |
| MakePromise(p.join().promises(2)), |
| MakePromise(p.join().promises(3))), |
| [](std::tuple<IntHdl, IntHdl, IntHdl, IntHdl> t) { |
| return std::get<0>(t); |
| }); |
| case 5: |
| return Map( |
| Join(MakePromise(p.join().promises(0)), |
| MakePromise(p.join().promises(1)), |
| MakePromise(p.join().promises(2)), |
| MakePromise(p.join().promises(3)), |
| MakePromise(p.join().promises(4))), |
| [](std::tuple<IntHdl, IntHdl, IntHdl, IntHdl, IntHdl> t) { |
| return std::get<0>(t); |
| }); |
| case 6: |
| return Map( |
| Join(MakePromise(p.join().promises(0)), |
| MakePromise(p.join().promises(1)), |
| MakePromise(p.join().promises(2)), |
| MakePromise(p.join().promises(3)), |
| MakePromise(p.join().promises(4)), |
| MakePromise(p.join().promises(5))), |
| [](std::tuple<IntHdl, IntHdl, IntHdl, IntHdl, IntHdl, IntHdl> |
| t) { return std::get<0>(t); }); |
| } |
| break; |
| case promise_fuzzer::Promise::kRace: |
| switch (p.race().promises_size()) { |
| case 1: |
| return Race(MakePromise(p.race().promises(0))); |
| case 2: |
| return Race(MakePromise(p.race().promises(0)), |
| MakePromise(p.race().promises(1))); |
| case 3: |
| return Race(MakePromise(p.race().promises(0)), |
| MakePromise(p.race().promises(1)), |
| MakePromise(p.race().promises(2))); |
| case 4: |
| return Race(MakePromise(p.race().promises(0)), |
| MakePromise(p.race().promises(1)), |
| MakePromise(p.race().promises(2)), |
| MakePromise(p.race().promises(3))); |
| case 5: |
| return Race(MakePromise(p.race().promises(0)), |
| MakePromise(p.race().promises(1)), |
| MakePromise(p.race().promises(2)), |
| MakePromise(p.race().promises(3)), |
| MakePromise(p.race().promises(4))); |
| case 6: |
| return Race(MakePromise(p.race().promises(0)), |
| MakePromise(p.race().promises(1)), |
| MakePromise(p.race().promises(2)), |
| MakePromise(p.race().promises(3)), |
| MakePromise(p.race().promises(4)), |
| MakePromise(p.race().promises(5))); |
| } |
| break; |
| case promise_fuzzer::Promise::kNever: |
| return Never<IntHdl>(); |
| case promise_fuzzer::Promise::kSleepFirstN: { |
| int n = p.sleep_first_n(); |
| return [n]() mutable -> Poll<IntHdl> { |
| if (n <= 0) return std::make_shared<int>(0); |
| n--; |
| return Pending{}; |
| }; |
| } |
| case promise_fuzzer::Promise::kCancelFromInside: |
| return [this]() -> Poll<IntHdl> { |
| this->activity_.reset(); |
| return Pending{}; |
| }; |
| case promise_fuzzer::Promise::kWaitOnceOnWaker: { |
| bool called = false; |
| auto config = p.wait_once_on_waker(); |
| return [this, config, called]() mutable -> Poll<IntHdl> { |
| if (!called) { |
| if (config.owning()) { |
| wakers_[config.waker()].push_back( |
| Activity::current()->MakeOwningWaker()); |
| } else { |
| wakers_[config.waker()].push_back( |
| Activity::current()->MakeNonOwningWaker()); |
| } |
| return Pending(); |
| } |
| return std::make_shared<int>(3); |
| }; |
| } |
| case promise_fuzzer::Promise::PromiseTypeCase::PROMISE_TYPE_NOT_SET: |
| break; |
| } |
| return [] { return std::make_shared<int>(42); }; |
| } |
| |
| // Activity under test |
| ActivityPtr activity_; |
| // Scheduled wakeup (may be nullptr if no wakeup scheduled) |
| std::function<void()> wakeup_; |
| // If we are certain of the final status, then that. Otherwise, nullopt if we |
| // don't know. |
| absl::optional<absl::Status> expected_status_; |
| // Has on_done been called? |
| bool done_ = false; |
| // Wakers that may be scheduled |
| std::map<int, std::vector<Waker>> wakers_; |
| }; |
| } // namespace |
| |
| } // namespace grpc_core |
| |
| DEFINE_PROTO_FUZZER(const promise_fuzzer::Msg& msg) { |
| grpc_core::Fuzzer().Run(msg); |
| } |