/*
 * Reproducer for RadeonSI get_output_as_const_value heap-buffer-overflow.
 *
 * The noop-blend analysis in si_nir_optim.c hardcodes a 4-component read
 * from nir_load_const_instr->value[], but the flexible array is only
 * allocated with num_components entries. A scalar fragment output folds
 * to a 1-component constant, causing a 24-byte over-read.
 *
 * Build Mesa with ASAN and MAX_FREELIST_SIZE=0 to make the OOB visible:
 *   sed -i 's/#define MAX_FREELIST_SIZE 512/#define MAX_FREELIST_SIZE 0/' src/util/ralloc.c
 *
 * Compile this reproducer:
 *   cc -o repro repro.c -lEGL -lGL -lgbm
 *
 * Run:
 *   LD_LIBRARY_PATH=/path/to/mesa-asan/install/lib/x86_64-linux-gnu \
 *   LIBGL_DRIVERS_PATH=/path/to/mesa-asan/install/lib/x86_64-linux-gnu/dri \
 *   MESA_SHADER_CACHE_DISABLE=true \
 *   ./repro /dev/dri/renderD128
 */

#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <fcntl.h>
#include <gbm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void die(const char *msg) { fprintf(stderr, "FATAL: %s\n", msg); exit(1); }

static GLuint compile_shader(GLenum type, const char *src) {
    GLuint s = glCreateShader(type);
    glShaderSource(s, 1, &src, NULL);
    glCompileShader(s);
    GLint ok;
    glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        char log[512];
        glGetShaderInfoLog(s, sizeof(log), NULL, log);
        fprintf(stderr, "Shader compile error: %s\n", log);
        exit(1);
    }
    return s;
}

int main(int argc, char **argv) {
    const char *dev = argc > 1 ? argv[1] : "/dev/dri/renderD128";

    /* GBM + EGL setup */
    int fd = open(dev, O_RDWR);
    if (fd < 0) die("cannot open DRI device");

    struct gbm_device *gbm = gbm_create_device(fd);
    if (!gbm) die("gbm_create_device failed");

    EGLDisplay dpy = eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm, NULL);
    if (dpy == EGL_NO_DISPLAY) die("eglGetPlatformDisplay failed");

    EGLint major, minor;
    if (!eglInitialize(dpy, &major, &minor)) die("eglInitialize failed");

    if (!eglBindAPI(EGL_OPENGL_API)) die("eglBindAPI failed");

    static const EGLint cfg_attr[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
        EGL_NONE
    };
    EGLConfig cfg;
    EGLint ncfg;
    if (!eglChooseConfig(dpy, cfg_attr, &cfg, 1, &ncfg) || ncfg == 0)
        die("eglChooseConfig failed");

    static const EGLint ctx_attr[] = {
        EGL_CONTEXT_MAJOR_VERSION, 3,
        EGL_CONTEXT_MINOR_VERSION, 2,
        EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
        EGL_NONE
    };
    EGLContext ctx = eglCreateContext(dpy, cfg, EGL_NO_CONTEXT, ctx_attr);
    if (ctx == EGL_NO_CONTEXT) die("eglCreateContext failed");

    if (!eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx))
        die("eglMakeCurrent failed");

    printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
    printf("GL_VERSION:  %s\n", glGetString(GL_VERSION));

    /* Fragment shader with scalar output */
    const char *vs_src =
        "#version 150\n"
        "in vec4 pos;\n"
        "void main() { gl_Position = pos; }\n";

    const char *fs_src =
        "#version 150\n"
        "uniform sampler2D tex;\n"
        "out float o;\n"
        "void main() { o = texture(tex, vec2(0.5)).r; }\n";

    GLuint vs = compile_shader(GL_VERTEX_SHADER, vs_src);
    GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fs_src);

    GLuint prog = glCreateProgram();
    glAttachShader(prog, vs);
    glAttachShader(prog, fs);
    glBindFragDataLocation(prog, 0, "o");
    glLinkProgram(prog);
    GLint ok;
    glGetProgramiv(prog, GL_LINK_STATUS, &ok);
    if (!ok) die("program link failed");
    glUseProgram(prog);
    printf("Shader compiled and linked.\n");

    /* R8 FBO (single-channel color attachment) */
    GLuint fbo, rbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_R8, 64, 64);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, rbo);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        die("FBO incomplete");

    /* Texture */
    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    unsigned char white[4*4*4];
    memset(white, 0xff, sizeof(white));
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, white);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glUniform1i(glGetUniformLocation(prog, "tex"), 0);

    /* Noop blend: DST_COLOR * src + ZERO * dst */
    glEnable(GL_BLEND);
    glBlendFunc(GL_DST_COLOR, GL_ZERO);
    glBlendEquation(GL_FUNC_ADD);

    /* VAO + VBO */
    GLuint vao, vbo;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    float verts[] = { -1,-1, 1,-1, -1,1, 1,1 };
    glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
    GLint loc = glGetAttribLocation(prog, "pos");
    glEnableVertexAttribArray(loc);
    glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 0, NULL);

    /* Draw — triggers si_check_blend_dst_sampler_noop → OOB read */
    glViewport(0, 0, 64, 64);
    printf("Drawing (triggers noop-blend analysis)...\n");
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glFinish();

    printf("Draw completed. If no ASAN report, the OOB was absorbed by the slab allocator.\n"
           "Rebuild Mesa with MAX_FREELIST_SIZE=0 to make it visible.\n");

    /* Cleanup */
    eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(dpy, ctx);
    eglTerminate(dpy);
    gbm_device_destroy(gbm);
    close(fd);
    return 0;
}
