LLVMFuzzerTestOneInput:
   12|  3.24k|extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
   13|       |    /* Create topic tree */
   14|  3.24k|    uWS::TopicTree<std::string, std::string_view> topicTree([](uWS::Subscriber *s, std::string &message, auto flags) {
   15|       |
   16|       |        /* Depending on what publishing we do below (with or without empty strings),
   17|       |         * this assumption can hold true or not. For now it should hold true */
   18|  3.24k|        if (!message.length()) {
   19|  3.24k|            free((void *) -1);
   20|  3.24k|        }
   21|       |
   22|       |        /* Break if we have no subscriptions (not really an error, just to bring more randomness) */
   23|  3.24k|        if (s->topics.size() == 0) {
   24|  3.24k|            return true;
   25|  3.24k|        }
   26|       |
   27|       |        /* Success */
   28|  3.24k|        return false;
   29|  3.24k|    });
   30|       |
   31|       |    /* Holder for all manually allocated subscribers */
   32|  3.24k|    std::map<uint32_t, uWS::Subscriber *> subscribers;
   33|       |
   34|       |    /* Iterate the padded fuzz as chunks */
   35|  3.24k|    makeChunked(makePadded(data, size), size, [&topicTree, &subscribers](const uint8_t *data, size_t size) {
   36|       |        /* We need at least 5 bytes */
   37|  3.24k|        if (size > 4) {
   38|       |            /* Last of all is a string */
   39|  3.24k|            std::string_view lastString((char *) data + 5, size - 5);
   40|       |            
   41|       |            /* Why not */
   42|  3.24k|            topicTree.lookupTopic(lastString);
   43|       |
   44|       |            /* First 4 bytes is the subscriber id */
   45|  3.24k|            uint32_t id;
   46|  3.24k|            memcpy(&id, data, 4);
   47|       |
   48|       |            /* Then one byte action */
   49|  3.24k|            if (data[4] == 'S') {
   50|       |
   51|       |                /* Some ridiculously long topics has to be cut short (OOM) */
   52|  3.24k|                if (lastString.length() > 512) {
   53|  3.24k|                    lastString = "too long!";
   54|  3.24k|                }
   55|       |
   56|       |                /* Subscribe */
   57|  3.24k|                if (subscribers.find(id) == subscribers.end()) {
   58|       |
   59|       |                    /* Limit number of subscribers to 100 (OOM) */
   60|  3.24k|                    if (subscribers.size() > 100) {
   61|  3.24k|                        return;
   62|  3.24k|                    }
   63|       |
   64|  3.24k|                    uWS::Subscriber *subscriber = topicTree.createSubscriber();
   65|  3.24k|                    subscribers[id] = subscriber;
   66|  3.24k|                    topicTree.subscribe(subscriber, lastString);
   67|  3.24k|                } else {
   68|       |                    /* Limit per subscriber subscriptions (OOM) */
   69|  3.24k|                    uWS::Subscriber *subscriber = subscribers[id];
   70|  3.24k|                    if (subscriber->topics.size() < 50) {
   71|  3.24k|                        topicTree.subscribe(subscriber, lastString);
   72|  3.24k|                    }
   73|  3.24k|                }
   74|  3.24k|            } else if (data[4] == 'U') {
   75|       |                /* Unsubscribe */
   76|  3.24k|                auto it = subscribers.find(id);
   77|  3.24k|                if (it != subscribers.end()) {
   78|  3.24k|                    topicTree.unsubscribe(it->second, lastString);
   79|  3.24k|                }
   80|  3.24k|            } else if (data[4] == 'F') {
   81|       |                /* Free subscriber */
   82|  3.24k|                auto it = subscribers.find(id);
   83|  3.24k|                if (it != subscribers.end()) {
   84|  3.24k|                    topicTree.freeSubscriber(it->second);
   85|  3.24k|                    subscribers.erase(it);
   86|  3.24k|                }
   87|  3.24k|            } else if (data[4] == 'A') {
   88|       |                /* Unsubscribe from all */
   89|  3.24k|                auto it = subscribers.find(id);
   90|  3.24k|                if (it != subscribers.end()) {
   91|  3.24k|                    std::vector<std::string> topics;
   92|  3.24k|                    for (auto *topic : it->second->topics) {
   93|  3.24k|                        topics.push_back(topic->name);
   94|  3.24k|                    }
   95|       |
   96|  3.24k|                    for (std::string &topic : topics) {
   97|  3.24k|                        topicTree.unsubscribe(it->second, topic);
   98|  3.24k|                    }
   99|  3.24k|                }
  100|  3.24k|            } else if (data[4] == 'O') {
  101|       |                /* Drain one socket */
  102|  3.24k|                auto it = subscribers.find(id);
  103|  3.24k|                if (it != subscribers.end()) {
  104|  3.24k|                    topicTree.drain(it->second);
  105|  3.24k|                }
  106|  3.24k|            } else if (data[4] == 'P') {
  107|       |                /* Publish only if we actually have data */
  108|  3.24k|                if (lastString.length()) {
  109|  3.24k|                    topicTree.publish(nullptr, lastString, std::string(lastString));
  110|  3.24k|                } else {
  111|       |                    /* We could use having more strings */
  112|  3.24k|                    topicTree.publish(nullptr, "", "anything");
  113|  3.24k|                }
  114|  3.24k|            } else {
  115|       |                /* Drain for everything else (OOM) */
  116|  3.24k|                topicTree.drain();
  117|  3.24k|            }
  118|  3.24k|        }
  119|  3.24k|    });
  120|       |
  121|       |    /* Remove any subscriber from the tree */
  122|  21.8k|    for (auto &p : subscribers) {
  ------------------
  |  Branch (122:18): [True: 21.8k, False: 3.24k]
  ------------------
  123|  21.8k|        topicTree.freeSubscriber(p.second);
  124|  21.8k|    }
  125|       |
  126|  3.24k|    return 0;
  127|  3.24k|}
TopicTree.cpp:_ZZ22LLVMFuzzerTestOneInputENK3$_0clIN3uWS9TopicTreeINSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEENS3_17basic_string_viewIcS6_EEE13IteratorFlagsEEEDaPNS1_10SubscriberERS9_T_:
   14|  2.95M|    uWS::TopicTree<std::string, std::string_view> topicTree([](uWS::Subscriber *s, std::string &message, auto flags) {
   15|       |
   16|       |        /* Depending on what publishing we do below (with or without empty strings),
   17|       |         * this assumption can hold true or not. For now it should hold true */
   18|  2.95M|        if (!message.length()) {
  ------------------
  |  Branch (18:13): [True: 0, False: 2.95M]
  ------------------
   19|      0|            free((void *) -1);
   20|      0|        }
   21|       |
   22|       |        /* Break if we have no subscriptions (not really an error, just to bring more randomness) */
   23|  2.95M|        if (s->topics.size() == 0) {
  ------------------
  |  Branch (23:13): [True: 2.37k, False: 2.95M]
  ------------------
   24|  2.37k|            return true;
   25|  2.37k|        }
   26|       |
   27|       |        /* Success */
   28|  2.95M|        return false;
   29|  2.95M|    });
TopicTree.cpp:_ZZ22LLVMFuzzerTestOneInputENK3$_1clEPKhm:
   35|  4.28M|    makeChunked(makePadded(data, size), size, [&topicTree, &subscribers](const uint8_t *data, size_t size) {
   36|       |        /* We need at least 5 bytes */
   37|  4.28M|        if (size > 4) {
  ------------------
  |  Branch (37:13): [True: 3.75M, False: 530k]
  ------------------
   38|       |            /* Last of all is a string */
   39|  3.75M|            std::string_view lastString((char *) data + 5, size - 5);
   40|       |            
   41|       |            /* Why not */
   42|  3.75M|            topicTree.lookupTopic(lastString);
   43|       |
   44|       |            /* First 4 bytes is the subscriber id */
   45|  3.75M|            uint32_t id;
   46|  3.75M|            memcpy(&id, data, 4);
   47|       |
   48|       |            /* Then one byte action */
   49|  3.75M|            if (data[4] == 'S') {
  ------------------
  |  Branch (49:17): [True: 1.12M, False: 2.62M]
  ------------------
   50|       |
   51|       |                /* Some ridiculously long topics has to be cut short (OOM) */
   52|  1.12M|                if (lastString.length() > 512) {
  ------------------
  |  Branch (52:21): [True: 20, False: 1.12M]
  ------------------
   53|     20|                    lastString = "too long!";
   54|     20|                }
   55|       |
   56|       |                /* Subscribe */
   57|  1.12M|                if (subscribers.find(id) == subscribers.end()) {
  ------------------
  |  Branch (57:21): [True: 968k, False: 155k]
  ------------------
   58|       |
   59|       |                    /* Limit number of subscribers to 100 (OOM) */
   60|   968k|                    if (subscribers.size() > 100) {
  ------------------
  |  Branch (60:25): [True: 659, False: 968k]
  ------------------
   61|    659|                        return;
   62|    659|                    }
   63|       |
   64|   968k|                    uWS::Subscriber *subscriber = topicTree.createSubscriber();
   65|   968k|                    subscribers[id] = subscriber;
   66|   968k|                    topicTree.subscribe(subscriber, lastString);
   67|   968k|                } else {
   68|       |                    /* Limit per subscriber subscriptions (OOM) */
   69|   155k|                    uWS::Subscriber *subscriber = subscribers[id];
   70|   155k|                    if (subscriber->topics.size() < 50) {
  ------------------
  |  Branch (70:25): [True: 153k, False: 1.92k]
  ------------------
   71|   153k|                        topicTree.subscribe(subscriber, lastString);
   72|   153k|                    }
   73|   155k|                }
   74|  2.62M|            } else if (data[4] == 'U') {
  ------------------
  |  Branch (74:24): [True: 20.5k, False: 2.60M]
  ------------------
   75|       |                /* Unsubscribe */
   76|  20.5k|                auto it = subscribers.find(id);
   77|  20.5k|                if (it != subscribers.end()) {
  ------------------
  |  Branch (77:21): [True: 17.5k, False: 2.98k]
  ------------------
   78|  17.5k|                    topicTree.unsubscribe(it->second, lastString);
   79|  17.5k|                }
   80|  2.60M|            } else if (data[4] == 'F') {
  ------------------
  |  Branch (80:24): [True: 1.01M, False: 1.59M]
  ------------------
   81|       |                /* Free subscriber */
   82|  1.01M|                auto it = subscribers.find(id);
   83|  1.01M|                if (it != subscribers.end()) {
  ------------------
  |  Branch (83:21): [True: 946k, False: 64.3k]
  ------------------
   84|   946k|                    topicTree.freeSubscriber(it->second);
   85|   946k|                    subscribers.erase(it);
   86|   946k|                }
   87|  1.59M|            } else if (data[4] == 'A') {
  ------------------
  |  Branch (87:24): [True: 19.3k, False: 1.57M]
  ------------------
   88|       |                /* Unsubscribe from all */
   89|  19.3k|                auto it = subscribers.find(id);
   90|  19.3k|                if (it != subscribers.end()) {
  ------------------
  |  Branch (90:21): [True: 17.8k, False: 1.53k]
  ------------------
   91|  17.8k|                    std::vector<std::string> topics;
   92|  58.4k|                    for (auto *topic : it->second->topics) {
  ------------------
  |  Branch (92:38): [True: 58.4k, False: 17.8k]
  ------------------
   93|  58.4k|                        topics.push_back(topic->name);
   94|  58.4k|                    }
   95|       |
   96|  58.4k|                    for (std::string &topic : topics) {
  ------------------
  |  Branch (96:45): [True: 58.4k, False: 17.8k]
  ------------------
   97|  58.4k|                        topicTree.unsubscribe(it->second, topic);
   98|  58.4k|                    }
   99|  17.8k|                }
  100|  1.57M|            } else if (data[4] == 'O') {
  ------------------
  |  Branch (100:24): [True: 4.96k, False: 1.57M]
  ------------------
  101|       |                /* Drain one socket */
  102|  4.96k|                auto it = subscribers.find(id);
  103|  4.96k|                if (it != subscribers.end()) {
  ------------------
  |  Branch (103:21): [True: 3.76k, False: 1.19k]
  ------------------
  104|  3.76k|                    topicTree.drain(it->second);
  105|  3.76k|                }
  106|  1.57M|            } else if (data[4] == 'P') {
  ------------------
  |  Branch (106:24): [True: 1.29M, False: 274k]
  ------------------
  107|       |                /* Publish only if we actually have data */
  108|  1.29M|                if (lastString.length()) {
  ------------------
  |  Branch (108:21): [True: 14.3k, False: 1.28M]
  ------------------
  109|  14.3k|                    topicTree.publish(nullptr, lastString, std::string(lastString));
  110|  1.28M|                } else {
  111|       |                    /* We could use having more strings */
  112|  1.28M|                    topicTree.publish(nullptr, "", "anything");
  113|  1.28M|                }
  114|  1.29M|            } else {
  115|       |                /* Drain for everything else (OOM) */
  116|   274k|                topicTree.drain();
  117|   274k|            }
  118|  3.75M|        }
  119|  4.28M|    });

TopicTree.cpp:_ZL11makeChunkedPKhmNSt3__18functionIFvS0_mEEE:
   28|  3.24k|static inline void makeChunked(const uint8_t *data, size_t size, std::function<void(const uint8_t *data, size_t size)> cb) {
   29|       |    /* First byte determines chunk size; 0 is all that remains, 1-255 is small chunk */
   30|  4.28M|    for (int i = 0; i < size; ) {
  ------------------
  |  Branch (30:21): [True: 4.28M, False: 3.24k]
  ------------------
   31|  4.28M|        unsigned int chunkSize = data[i++];
   32|  4.28M|        if (!chunkSize) {
  ------------------
  |  Branch (32:13): [True: 283, False: 4.28M]
  ------------------
   33|    283|            chunkSize = size - i;
   34|  4.28M|        } else {
   35|  4.28M|            chunkSize = std::min<int>(chunkSize, size - i);
   36|  4.28M|        }
   37|       |
   38|  4.28M|        cb(data + i, chunkSize);
   39|  4.28M|        i += chunkSize;
   40|  4.28M|    }
   41|  3.24k|}
TopicTree.cpp:_ZL10makePaddedPKhm:
   11|  3.24k|static inline const uint8_t *makePadded(const uint8_t *data, size_t size) {
   12|  3.24k|    static int paddedLength = 512 * 1024;
   13|  3.24k|    static char *padded = new char[128 + paddedLength + 128];
   14|       |
   15|       |    /* Increase landing area if required */
   16|  3.24k|    if (paddedLength < size) {
  ------------------
  |  Branch (16:9): [True: 18, False: 3.22k]
  ------------------
   17|     18|        delete [] padded;
   18|     18|        paddedLength = size;
   19|     18|        padded = new char [128 + paddedLength + 128];
   20|     18|    }
   21|       |
   22|  3.24k|    memcpy(padded + 128, data, size);
   23|       |
   24|  3.24k|    return (uint8_t *) padded + 128;
   25|  3.24k|}

_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEEC2ENS1_8functionIFbPNS_10SubscriberERS7_NSA_13IteratorFlagsEEEE:
  148|  3.24k|    TopicTree(std::function<bool(Subscriber *, T &, IteratorFlags)> cb) : cb(cb) {
  149|       |
  150|  3.24k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE14freeSubscriberEPNS_10SubscriberE:
  223|   968k|    void freeSubscriber(Subscriber *s) {
  224|       |
  225|       |        /* I guess we call this one even if we are not subscribers */
  226|   968k|        if (!s) {
  ------------------
  |  Branch (226:13): [True: 0, False: 968k]
  ------------------
  227|      0|            return;
  228|      0|        }
  229|       |
  230|       |        /* For all topics, unsubscribe */
  231|   980k|        for (Topic *topicPtr : s->topics) {
  ------------------
  |  Branch (231:30): [True: 980k, False: 968k]
  ------------------
  232|       |            /* If we are the last subscriber, simply remove the whole topic */
  233|   980k|            if (topicPtr->size() == 1) {
  ------------------
  |  Branch (233:17): [True: 129k, False: 850k]
  ------------------
  234|   129k|                topics.erase(topicPtr->name);
  235|   850k|            } else {
  236|       |                /* Otherwise just remove us */
  237|   850k|                topicPtr->erase(s);
  238|   850k|            }
  239|   980k|        }
  240|       |
  241|       |        /* We also need to unlink us */
  242|   968k|        if (s->needsDrainage()) {
  ------------------
  |  Branch (242:13): [True: 3.54k, False: 964k]
  ------------------
  243|  3.54k|            unlinkDrainableSubscriber(s);
  244|  3.54k|        }
  245|       |
  246|   968k|        delete s;
  247|   968k|    }
_ZN3uWS10Subscriber13needsDrainageEv:
   74|  1.05M|    bool needsDrainage() {
   75|  1.05M|        return numMessageIndices;
   76|  1.05M|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE25unlinkDrainableSubscriberEPNS_10SubscriberE:
  133|  89.2k|    void unlinkDrainableSubscriber(Subscriber *s) {
  134|  89.2k|        if (s->prev) {
  ------------------
  |  Branch (134:13): [True: 83.7k, False: 5.44k]
  ------------------
  135|  83.7k|            s->prev->next = s->next;
  136|  83.7k|        }
  137|  89.2k|        if (s->next) {
  ------------------
  |  Branch (137:13): [True: 33.5k, False: 55.7k]
  ------------------
  138|  33.5k|            s->next->prev = s->prev;
  139|  33.5k|        }
  140|       |        /* If we are the head, then we also need to reset the head */
  141|  89.2k|        if (drainableSubscribers == s) {
  ------------------
  |  Branch (141:13): [True: 5.44k, False: 83.7k]
  ------------------
  142|  5.44k|            drainableSubscribers = s->next;
  143|  5.44k|        }
  144|  89.2k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE11lookupTopicES9_:
  153|  4.95M|    Topic *lookupTopic(std::string_view topic) {
  154|  4.95M|        auto it = topics.find(topic);
  155|  4.95M|        if (it == topics.end()) {
  ------------------
  |  Branch (155:13): [True: 616k, False: 4.33M]
  ------------------
  156|   616k|            return nullptr;
  157|   616k|        }
  158|  4.33M|        return it->second.get();
  159|  4.95M|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE16createSubscriberEv:
  218|   968k|    Subscriber *createSubscriber() {
  219|   968k|        return new Subscriber();
  220|   968k|    }
_ZN3uWS10SubscriberC2Ev:
   54|   968k|    Subscriber() = default;
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE9subscribeEPNS_10SubscriberES9_:
  162|  1.12M|    Topic *subscribe(Subscriber *s, std::string_view topic) {
  163|       |        /* Notify user that they are doing something wrong here */
  164|  1.12M|        checkIteratingSubscriber(s);
  165|       |
  166|       |        /* Lookup or create new topic */
  167|  1.12M|        Topic *topicPtr = lookupTopic(topic);
  168|  1.12M|        if (!topicPtr) {
  ------------------
  |  Branch (168:13): [True: 166k, False: 954k]
  ------------------
  169|   166k|            Topic *newTopic = new Topic(topic);
  170|   166k|            topics.insert({std::string_view(newTopic->name.data(), newTopic->name.length()), std::unique_ptr<Topic>(newTopic)});
  171|   166k|            topicPtr = newTopic;
  172|   166k|        }
  173|       |
  174|       |        /* Insert us in topic, insert topic in us */
  175|  1.12M|        auto [it, inserted] = s->topics.insert(topicPtr);
  176|  1.12M|        if (!inserted) {
  ------------------
  |  Branch (176:13): [True: 69.9k, False: 1.05M]
  ------------------
  177|  69.9k|            return nullptr;
  178|  69.9k|        }
  179|  1.05M|        topicPtr->insert(s);
  180|       |
  181|       |        /* Success */
  182|  1.05M|        return topicPtr;
  183|  1.12M|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE24checkIteratingSubscriberEPNS_10SubscriberE:
  105|  1.19M|    void checkIteratingSubscriber(Subscriber *s) {
  106|       |        /* Notify user that they are doing something wrong here */
  107|  1.19M|        if (iteratingSubscriber == s) {
  ------------------
  |  Branch (107:13): [True: 0, False: 1.19M]
  ------------------
  108|      0|            std::cerr << "Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!" << std::endl;
  109|      0|            std::terminate();
  110|      0|        }
  111|  1.19M|    }
_ZN3uWS5TopicC2ENSt3__117basic_string_viewIcNS1_11char_traitsIcEEEE:
   41|   166k|    Topic(std::string_view topic) : name(topic) {
   42|       |
   43|   166k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE11unsubscribeEPNS_10SubscriberES9_:
  186|  76.0k|    std::tuple<bool, bool, int> unsubscribe(Subscriber *s, std::string_view topic) {
  187|       |        /* Notify user that they are doing something wrong here */
  188|  76.0k|        checkIteratingSubscriber(s);
  189|       |
  190|       |        /* Lookup topic */
  191|  76.0k|        Topic *topicPtr = lookupTopic(topic);
  192|  76.0k|        if (!topicPtr) {
  ------------------
  |  Branch (192:13): [True: 1.48k, False: 74.5k]
  ------------------
  193|       |            /* If the topic doesn't exist we are assumed to still be subscribers of something */
  194|  1.48k|            return {false, false, -1};
  195|  1.48k|        }
  196|       |
  197|       |        /* Erase from our list first */
  198|  74.5k|        if (s->topics.erase(topicPtr) == 0) {
  ------------------
  |  Branch (198:13): [True: 3.45k, False: 71.1k]
  ------------------
  199|  3.45k|            return {false, false, -1};
  200|  3.45k|        }
  201|       |
  202|       |        /* Remove us from topic */
  203|  71.1k|        topicPtr->erase(s);
  204|       |
  205|  71.1k|        int newCount = (int) topicPtr->size();
  206|       |
  207|       |        /* If there is no subscriber to this topic, remove it */
  208|  71.1k|        if (!topicPtr->size()) {
  ------------------
  |  Branch (208:13): [True: 37.4k, False: 33.6k]
  ------------------
  209|       |            /* Unique_ptr deletes the topic */
  210|  37.4k|            topics.erase(topic);
  211|  37.4k|        }
  212|       |
  213|       |        /* If we don't hold any topics we are to be freed altogether */
  214|  71.1k|        return {true, s->topics.size() == 0, newCount};
  215|  74.5k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE5drainEPNS_10SubscriberE:
  250|  86.5k|    void drain(Subscriber *s) {
  251|       |        /* The list is undefined and cannot be touched unless needsDrainage(). */
  252|  86.5k|        if (s->needsDrainage()) {
  ------------------
  |  Branch (252:13): [True: 85.6k, False: 887]
  ------------------
  253|       |            /* This function differs from drainImpl by properly unlinking
  254|       |            * the subscriber from drainableSubscribers. drainImpl does not. */
  255|  85.6k|            unlinkDrainableSubscriber(s);
  256|       |
  257|       |            /* This one always resets needsDrainage before it calls any cb's.
  258|       |             * Otherwise we would stackoverflow when sending after publish but before drain. */
  259|  85.6k|            drainImpl(s);
  260|       |            
  261|       |            /* If we drained last subscriber, also clear outgoingMessages */
  262|  85.6k|            if (!drainableSubscribers) {
  ------------------
  |  Branch (262:17): [True: 805, False: 84.8k]
  ------------------
  263|    805|                outgoingMessages.clear();
  264|    805|            }
  265|  85.6k|        }
  266|  86.5k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE9drainImplEPNS_10SubscriberE:
  114|   192k|    void drainImpl(Subscriber *s) {
  115|       |        /* Before we call cb we need to make sure this subscriber will not report needsDrainage()
  116|       |         * since WebSocket::send will call drain from within the cb in that case.*/
  117|   192k|        int numMessageIndices = s->numMessageIndices;
  118|   192k|        s->numMessageIndices = 0;
  119|       |
  120|       |        /* Then we emit cb */
  121|  3.14M|        for (int i = 0; i < numMessageIndices; i++) {
  ------------------
  |  Branch (121:25): [True: 2.95M, False: 189k]
  ------------------
  122|  2.95M|            T &outgoingMessage = outgoingMessages[s->messageIndices[i]];
  123|       |
  124|  2.95M|            int flags = (i == numMessageIndices - 1) ? LAST : 0;
  ------------------
  |  Branch (124:25): [True: 191k, False: 2.76M]
  ------------------
  125|       |
  126|       |            /* Returning true will stop drainage short (such as when backpressure is too high) */
  127|  2.95M|            if (cb(s, outgoingMessage, (IteratorFlags)(flags | (i == 0 ? FIRST : 0)))) {
  ------------------
  |  Branch (127:17): [True: 2.37k, False: 2.95M]
  |  Branch (127:65): [True: 192k, False: 2.76M]
  ------------------
  128|  2.37k|                break;
  129|  2.37k|            }
  130|  2.95M|        }
  131|   192k|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE7publishEPNS_10SubscriberES9_OS7_:
  305|  1.29M|    bool publish(Subscriber *sender, std::string_view topic, T &&message) {
  306|       |        /* Do we even have this topic? */
  307|  1.29M|        auto it = topics.find(topic);
  308|  1.29M|        if (it == topics.end()) {
  ------------------
  |  Branch (308:13): [True: 15.7k, False: 1.28M]
  ------------------
  309|  15.7k|            return false;
  310|  15.7k|        }
  311|       |
  312|       |        /* If we have more than 65k messages we need to drain every socket. */
  313|  1.28M|        if (outgoingMessages.size() == UINT16_MAX) {
  ------------------
  |  Branch (313:13): [True: 3, False: 1.28M]
  ------------------
  314|       |            /* If there is a socket that is currently corked, this will be ugly as all sockets will drain
  315|       |             * to their own backpressure */
  316|      3|            drain();
  317|      3|        }
  318|       |
  319|       |        /* If nobody references this message, don't buffer it */
  320|  1.28M|        bool referencedMessage = false;
  321|       |
  322|       |        /* For all subscribers in topic */
  323|  2.97M|        for (Subscriber *s : *it->second) {
  ------------------
  |  Branch (323:28): [True: 2.97M, False: 1.28M]
  ------------------
  324|       |
  325|       |            /* If we are sender then ignore us */
  326|  2.97M|            if (sender != s) {
  ------------------
  |  Branch (326:17): [True: 2.97M, False: 0]
  ------------------
  327|       |
  328|       |                /* At least one subscriber wants this message */
  329|  2.97M|                referencedMessage = true;
  330|       |
  331|       |                /* If we already have too many outgoing messages on this subscriber, drain it now */
  332|  2.97M|                if (s->numMessageIndices == 32) {
  ------------------
  |  Branch (332:21): [True: 82.8k, False: 2.88M]
  ------------------
  333|       |                    /* This one does not need to check needsDrainage here but still does. */
  334|  82.8k|                    drain(s);
  335|  82.8k|                }
  336|       |
  337|       |                /* Finally we can continue */
  338|  2.97M|                s->messageIndices[s->numMessageIndices++] = (uint16_t)outgoingMessages.size();
  339|       |                /* First message adds subscriber to list of drainable subscribers */
  340|  2.97M|                if (s->numMessageIndices == 1) {
  ------------------
  |  Branch (340:21): [True: 195k, False: 2.77M]
  ------------------
  341|       |                    /* Insert us in the head of drainable subscribers */
  342|   195k|                    s->next = drainableSubscribers;
  343|   195k|                    s->prev = nullptr;
  344|   195k|                    if (s->next) {
  ------------------
  |  Branch (344:25): [True: 188k, False: 7.08k]
  ------------------
  345|   188k|                        s->next->prev = s;
  346|   188k|                    }
  347|   195k|                    drainableSubscribers = s;
  348|   195k|                }
  349|  2.97M|            }
  350|  2.97M|        }
  351|       |
  352|       |        /* Push this message and return with success */
  353|  1.28M|        if (referencedMessage) {
  ------------------
  |  Branch (353:13): [True: 1.28M, False: 0]
  ------------------
  354|  1.28M|            outgoingMessages.emplace_back(message);
  355|  1.28M|        }
  356|       |
  357|       |        /* Success if someone wants it */
  358|  1.28M|        return referencedMessage;
  359|  1.29M|    }
_ZN3uWS9TopicTreeINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS1_17basic_string_viewIcS4_EEE5drainEv:
  269|   274k|    void drain() {
  270|   274k|        if (drainableSubscribers) {
  ------------------
  |  Branch (270:13): [True: 5.82k, False: 269k]
  ------------------
  271|       |            /* Drain one socket a time */
  272|   112k|            for (Subscriber *s = drainableSubscribers; s; s = s->next) {
  ------------------
  |  Branch (272:56): [True: 106k, False: 5.82k]
  ------------------
  273|       |                /* Instead of unlinking every single subscriber, we just leave the list undefined
  274|       |                 * and reset drainableSubscribers ptr below. */
  275|   106k|                drainImpl(s);
  276|   106k|            }
  277|       |            /* Drain always clears drainableSubscribers and outgoingMessages */
  278|  5.82k|            drainableSubscribers = nullptr;
  279|  5.82k|            outgoingMessages.clear();
  280|  5.82k|        }
  281|   274k|    }

