root/lib/kunit/static_stub.c
// SPDX-License-Identifier: GPL-2.0
/*
 * KUnit function redirection (static stubbing) API.
 *
 * Copyright (C) 2022, Google LLC.
 * Author: David Gow <davidgow@google.com>
 */

#include <kunit/test.h>
#include <kunit/static_stub.h>
#include "hooks-impl.h"


/* Context for a static stub. This is stored in the resource data. */
struct kunit_static_stub_ctx {
        void *real_fn_addr;
        void *replacement_addr;
};

static void __kunit_static_stub_resource_free(struct kunit_resource *res)
{
        kfree(res->data);
}

/* Matching function for kunit_find_resource(). match_data is real_fn_addr. */
static bool __kunit_static_stub_resource_match(struct kunit *test,
                                                struct kunit_resource *res,
                                                void *match_real_fn_addr)
{
        /* This pointer is only valid if res is a static stub resource. */
        struct kunit_static_stub_ctx *ctx = res->data;

        /* Make sure the resource is a static stub resource. */
        if (res->free != &__kunit_static_stub_resource_free)
                return false;

        return ctx->real_fn_addr == match_real_fn_addr;
}

/* Hook to return the address of the replacement function. */
void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr)
{
        struct kunit_resource *res;
        struct kunit_static_stub_ctx *ctx;
        void *replacement_addr;

        res = kunit_find_resource(test,
                                  __kunit_static_stub_resource_match,
                                  real_fn_addr);

        if (!res)
                return NULL;

        ctx = res->data;
        replacement_addr = ctx->replacement_addr;
        kunit_put_resource(res);
        return replacement_addr;
}

void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr)
{
        struct kunit_resource *res;

        KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
                                "Tried to deactivate a NULL stub.");

        /* Look up the existing stub for this function. */
        res = kunit_find_resource(test,
                                  __kunit_static_stub_resource_match,
                                  real_fn_addr);

        /* Error out if the stub doesn't exist. */
        KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL,
                                "Tried to deactivate a nonexistent stub.");

        /* Free the stub. We 'put' twice, as we got a reference
         * from kunit_find_resource()
         */
        kunit_remove_resource(test, res);
        kunit_put_resource(res);
}
EXPORT_SYMBOL_GPL(kunit_deactivate_static_stub);

/* Helper function for kunit_activate_static_stub(). The macro does
 * typechecking, so use it instead.
 */
void __kunit_activate_static_stub(struct kunit *test,
                                  void *real_fn_addr,
                                  void *replacement_addr)
{
        struct kunit_static_stub_ctx *ctx;
        struct kunit_resource *res;

        KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
                                "Tried to activate a stub for function NULL");

        /* If the replacement address is NULL, deactivate the stub. */
        if (!replacement_addr) {
                kunit_deactivate_static_stub(test, real_fn_addr);
                return;
        }

        /* Look up any existing stubs for this function, and replace them. */
        res = kunit_find_resource(test,
                                  __kunit_static_stub_resource_match,
                                  real_fn_addr);
        if (res) {
                ctx = res->data;
                ctx->replacement_addr = replacement_addr;

                /* We got an extra reference from find_resource(), so put it. */
                kunit_put_resource(res);
        } else {
                ctx = kmalloc_obj(*ctx);
                KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
                ctx->real_fn_addr = real_fn_addr;
                ctx->replacement_addr = replacement_addr;
                res = kunit_alloc_resource(test, NULL,
                                     &__kunit_static_stub_resource_free,
                                     GFP_KERNEL, ctx);
        }
}
EXPORT_SYMBOL_GPL(__kunit_activate_static_stub);