/* Copyright (c) 2023 Contrast Security, Inc.  See
 * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */

#include "cs__assess_basic_object.h"
#include "../cs__common/cs__common.h"
#include "../cs__scope/cs__scope.h"
#include <ruby.h>

/*
 * Source code of instance_eval:
 *
 *                static VALUE
 *  rb_obj_instance_eval_internal(int argc, const VALUE *argv, VALUE self)
 *  {
 *      VALUE klass = singleton_class_for_eval(self);
 *      return specific_eval(argc, argv, klass, self, RB_PASS_CALLED_KEYWORDS);
 *  }
 */

VALUE contrast_check_and_register_instance_patch(const char *module_name,
                                                 const char *method_name,
                                                 VALUE(c_fn)(const int, VALUE *,
                                                             const VALUE));

void contrast_assess_instance_eval_trigger_check(VALUE self, VALUE source,
                                                 VALUE ret) {
    rb_funcall(basic_eval_trigger, instance_trigger_check_method, 3, self,
               source, ret);
}

VALUE
contrast_assess_basic_object_instance_eval(const int argc, const VALUE *argv,
                                           const VALUE self) {

    if (RTEST(rb_funcall(contrast_patcher(), rb_sym_skip_assess_analysis, 0))) {
        return rb_obj_instance_eval(argc, argv, self);
    }

    VALUE nested_scope = inst_methods_in_cntr_scope(contrast_patcher(), 0);

    /* Enter scope */
    inst_methods_enter_cntr_scope(contrast_patcher(), 0);

    /* Call the source: */
    VALUE ret = rb_obj_instance_eval(argc, argv, self);

    if (nested_scope == Qfalse && argc > 0) {
        VALUE data = argv[0];
        contrast_assess_instance_eval_trigger_check(self, data, ret);
    }

    /* Exit scope */
    inst_methods_exit_cntr_scope(contrast_patcher(), 0);

    return ret;
}

void Init_cs__assess_basic_object(void) {
    basic_eval_trigger =
        rb_define_class_under(core_assess, "EvalTrigger", rb_cObject);
    instance_trigger_check_method = rb_intern("instance_eval_trigger_check");

    /* We don't keep a reference to the underlying method.
     * Instead, we call rb_obj_instance_eval directly.
     * This should work an overwhelming majority of the time,
     * but if someone else patched BasicObject#instance_eval,
     * IDK if this is intentional... noting it.  -ajm
     */
    contrast_check_and_register_instance_patch(
        "BasicObject", "instance_eval",
        contrast_assess_basic_object_instance_eval);
}