Certain errors (program linking) not caught by glDebugMessageCallback

The title is self-explanatory. Information about my platform isn’t necessary, because OpenGL code should run universally exactly the same on all operating systems that support OpenGL. This happens specifically when using glGetProgramiv. I still display the error, however I display it in the wrong place. Shader errors are caught and displayed by the custom callback. The error in this case has to do with the number of components of a vertex attribute shared between vertex and fragment shaders. Here is the source code for said project:

#include <stdlib.h>
#include <assert.h>
#include "glad/glad.h"
#include <GLFW/glfw3.h>
#define CGLM_OMIT_NS_FROM_STRUCT_API
#include <cglm/struct.h>
#include <math.h>

#define max_error_msg_size 512

struct vertex {
    vec3s position;
};
 
static const char* vertex_shader_text =
"#version 330 core\n"
"layout (location = 0) in vec3 vertex_position;\n"
"uniform mat4 transform;\n"
"out vec3 fragment_position;\n"
"void main()\n"
"{\n"
"    gl_Position = transform * vec4(vertex_position, 1.0);\n"
"    fragment_position = vertex_position;\n"
"}\n";
 
static const char* fragment_shader_text =
"#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 fragment_position;\n"
"void main()\n"
"{\n"
"    FragColor = fragment_position;\n"
"}\n";

#define log(x_, ...) fprintf(stderr, x_ "\n", ##__VA_ARGS__)
 
static void gl_debug_callback(
    GLenum source,
    GLenum type,
    GLuint id,
    GLenum severity,
    GLsizei length, const GLchar* message,
    const void* userParam
) {
    (void) source;
    (void) severity;
    (void) type;
    (void) id;
    (void) userParam;

    log("(opengl) %.*s", (int) length, message);
}

static void glfw_error_callback(int error, const char* description)
{
    (void) error;
    log("(glfw) %s", description);
}

#define max_indices 1024
#define max_vertices 1024

#define window_width 800
#define window_height 640

int main(void)
{
    bool success = true;
 
    glfwSetErrorCallback(glfw_error_callback);
 
    if (!glfwInit()) {
        success = false;
        goto end;
    }

    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
 
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Simple example", NULL, NULL);
    if (window == NULL) {
        success = false;
        goto glfw_delete;
    }
 
    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        log("GLAD initialization failed");
        goto glfw_window_delete;
    }

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEBUG_OUTPUT);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    glDebugMessageCallback((GLDEBUGPROC) gl_debug_callback, NULL);

    glfwSwapInterval(1);

    size_t indices_len = 3;
    unsigned int indices[max_indices];
    size_t vertices_len = 3;
    struct vertex vertices[max_vertices];

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
 
    GLuint ibo;
    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_DYNAMIC_DRAW);

    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    {
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);

        static_assert(sizeof(struct vertex) == sizeof(struct { vec3s _a;}), "Fuck");

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*) offsetof(struct vertex, position));
    }
    glBindVertexArray(0);

    GLuint program;
    {
        int shader_success = GL_TRUE;

        GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        {
            glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
            glCompileShader(vertex_shader);
            glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &shader_success);

            if (shader_success == GL_FALSE) {
                goto shader_end;
            }
        }

        GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
        {
            glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
            glCompileShader(fragment_shader);
            glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &shader_success);

            if (shader_success == GL_FALSE) {
                goto vertex_shader_delete;
            }
        }

        program = glCreateProgram();
        {
            glAttachShader(program, vertex_shader);
            glAttachShader(program, fragment_shader);
            glLinkProgram(program);
            glGetProgramiv(program, GL_LINK_STATUS, &shader_success);

            if (shader_success == GL_FALSE) {
                GLsizei log_length = 0;
                GLchar message[1024];
                glGetProgramInfoLog(program, 1024, &log_length, message);
                log("(opengl) %.*s", log_length, message);
                goto fragment_shader_delete;
            }
        }

    fragment_shader_delete:
        glDeleteShader(fragment_shader);
    vertex_shader_delete:
        glDeleteShader(vertex_shader);
    shader_end:
        if (shader_success == GL_FALSE) {
            success = false;
            goto gl_vbo_delete;
        }
    }
 
    GLint transform_location = glGetUniformLocation(program, "transform");

    bool click = false;

    while (!glfwWindowShouldClose(window)) {
        const float scale = (sinf(2.0f * M_PI * (float) glfwGetTime() / 1000.0f) + 1.0f) / 2.0f;
        mat4s transform = glms_scale(mat4_identity(), (vec3s) { .x = scale, .y = scale, .z = scale });
        transform = glms_rotate(transform, (float) glfwGetTime(), (vec3s) { .x = 0.0f, .y = 0.0f, .z = 1.0f });

        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
            break;
        }

        vec4s pos;
        {
            double x = 0.0;
            double y = 0.0;
            glfwGetCursorPos(window, &x, &y);
            pos.x = x;
            pos.y = y;
        }
        pos.x = 2.0f * pos.x / (double) window_width - 1.0f;
        pos.y = 1.0f - 2.0f * pos.y / (double) window_height;
        pos = mat4_mulv(mat4_inv(transform), pos);

        if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
            if (!click) {
                const size_t prev_vertices_len = vertices_len;

                const float size = 0.2f;

                vertices[vertices_len++] = (struct vertex) { .position = { .x = (float) pos.x - size / 2.0f, .y = (float) pos.y - size / 2.0f } };
                vertices[vertices_len++] = (struct vertex) { .position = { .x = (float) pos.x - size / 2.0f, .y = (float) pos.y + size / 2.0f } };
                vertices[vertices_len++] = (struct vertex) { .position = { .x = (float) pos.x + size / 2.0f, .y = (float) pos.y - size / 2.0f } };
                vertices[vertices_len++] = (struct vertex) { .position = { .x = (float) pos.x + size / 2.0f, .y = (float) pos.y + size / 2.0f } };
                indices[indices_len++] = prev_vertices_len + 0;
                indices[indices_len++] = prev_vertices_len + 1;
                indices[indices_len++] = prev_vertices_len + 2;
                indices[indices_len++] = prev_vertices_len + 1;
                indices[indices_len++] = prev_vertices_len + 2;
                indices[indices_len++] = prev_vertices_len + 3;

                glBindVertexArray(vao);
                {
                    glBindBuffer(GL_ARRAY_BUFFER, vbo);
                    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices[0]) * vertices_len, vertices);
                    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
                    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(indices[0]) * indices_len, indices);
                }
                glBindVertexArray(0);

                click = false;
            }
        } else {
            click = false;
        }

        glViewport(0, 0, window_width, window_height);
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
        const mat4s model = mat4_identity();
        const mat4s transform_final = mat4_mul(transform, model);
        glUseProgram(program);
        glUniformMatrix4fv(transform_location, 1, GL_FALSE, (const float*) transform_final.raw);
        glBindVertexArray(vao);
        {
            glDrawElements(GL_TRIANGLES, indices_len, GL_UNSIGNED_INT, (void*) 0);
        }
        glBindVertexArray(0);
 
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

// gl_program_delete:
    glDeleteProgram(program);
gl_vbo_delete:
    glDeleteBuffers(1, &vbo);
// gl_ibo_delete:
    glDeleteBuffers(1, &ibo);
// gl_vao_delete:
    glDeleteBuffers(1, &vao);
glfw_window_delete:
    glfwDestroyWindow(window);
glfw_delete:
    glfwTerminate();
end:
    return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

Errors in compilation and linking aren’t errors that would be reported by glGetError or glDebugMessageCallback. No GL error is generated unless you try to render using the faulty program.

It’s entirely legitimate to use the compilation and linking process to determine the validity of a candidate program, testing several alternative options until one of them produces a valid program, then using it. There’s no reason why the unsuccessful attempts should be treated as errors.