root/lib/kunit/bug.c
// SPDX-License-Identifier: GPL-2.0
/*
 * KUnit helpers for backtrace suppression
 *
 * Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com>
 * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
 */

#include <kunit/resource.h>
#include <linux/export.h>
#include <linux/rculist.h>
#include <linux/sched.h>
#include <linux/sched/task.h>
#include <linux/spinlock.h>

#include "hooks-impl.h"

struct kunit_suppressed_warning {
        struct list_head node;
        struct task_struct *task;
        struct kunit *test;
        atomic_t counter;
};

static LIST_HEAD(suppressed_warnings);
static DEFINE_SPINLOCK(suppressed_warnings_lock);

static void kunit_suppress_warning_remove(struct kunit_suppressed_warning *w)
{
        unsigned long flags;

        spin_lock_irqsave(&suppressed_warnings_lock, flags);
        list_del_rcu(&w->node);
        spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
        put_task_struct(w->task);
}

KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup,
                            kunit_suppress_warning_remove,
                            struct kunit_suppressed_warning *);

bool kunit_has_active_suppress_warning(void)
{
        return __kunit_is_suppressed_warning_impl(false);
}
EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning);

struct kunit_suppressed_warning *
kunit_start_suppress_warning(struct kunit *test)
{
        struct kunit_suppressed_warning *w;
        unsigned long flags;
        int ret;

        if (kunit_has_active_suppress_warning()) {
                KUNIT_FAIL(test, "Another suppression block is already active");
                return NULL;
        }

        w = kunit_kzalloc(test, sizeof(*w), GFP_KERNEL);
        if (!w) {
                KUNIT_FAIL(test, "Failed to allocate suppression handle.");
                return NULL;
        }

        w->task = get_task_struct(current);
        w->test = test;

        spin_lock_irqsave(&suppressed_warnings_lock, flags);
        list_add_rcu(&w->node, &suppressed_warnings);
        spin_unlock_irqrestore(&suppressed_warnings_lock, flags);

        ret = kunit_add_action_or_reset(test,
                                        kunit_suppress_warning_cleanup, w);
        if (ret) {
                KUNIT_FAIL(test, "Failed to add suppression cleanup action.");
                return NULL;
        }

        return w;
}
EXPORT_SYMBOL_GPL(kunit_start_suppress_warning);

void kunit_end_suppress_warning(struct kunit *test,
                                struct kunit_suppressed_warning *w)
{
        if (!w)
                return;
        kunit_release_action(test, kunit_suppress_warning_cleanup, w);
}
EXPORT_SYMBOL_GPL(kunit_end_suppress_warning);

void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp)
{
        if (*wp)
                kunit_end_suppress_warning((*wp)->test, *wp);
}
EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup);

int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w)
{
        return w ? atomic_read(&w->counter) : 0;
}
EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count);

bool __kunit_is_suppressed_warning_impl(bool count)
{
        struct kunit_suppressed_warning *w;

        guard(rcu)();
        list_for_each_entry_rcu(w, &suppressed_warnings, node) {
                if (w->task == current) {
                        if (count)
                                atomic_inc(&w->counter);
                        return true;
                }
        }

        return false;
}