// gcc12 -o uninitcheck -g -ftrivial-auto-var-init=pattern uninitcheck.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for readlink

static void __attribute__ ((noinline)) _imp_unass_error_str(char *vs) {
  void *caller_addr = __builtin_return_address(1);
  char exe_path[1024];
  char addr_str[32];
  FILE *pipe;
  char func_line[512] = { '\0' };
  char loc_line[256];
  ssize_t len;
  char cmd[2048];

  // or could use global imp_argv[0] below:
  if ((len = readlink("/proc/self/exe", exe_path, sizeof(exe_path)-1)) == -1) { perror("readlink"); exit(1); }
  exe_path[len] = '\0';

  sprintf(addr_str, "0x%lx", (unsigned long)caller_addr);

  snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %s", exe_path, addr_str);
  fprintf(stderr, "Execute: %s\n", cmd);
  if (!(pipe = popen(cmd, "r"))) { perror("popen"); exit(1); }

  (void)fgets(func_line, sizeof(func_line), pipe);
  if (fgets(loc_line, sizeof(loc_line), pipe)) {
    loc_line[strcspn(loc_line, "\n")] = 0;
    fprintf(stderr, "* Unassigned variable %s ", vs);
    if (func_line[0] != '\0') {
      func_line[strcspn(func_line, "\n")] = 0;
      if (strlen(func_line) > 0 && strcmp(func_line, "??") != 0) {
        fprintf(stderr, "detected in procedure %s ", func_line);
      }
    }
    fprintf(stderr, "at %s\n", loc_line);
  }
  pclose(pipe);
  exit(1);
}

static void __attribute__ ((noinline)) _imp_unass_error_no_str(void) {
  void *caller_addr = __builtin_return_address(1);
  char exe_path[1024];
  char addr_str[32];
  FILE *pipe;
  char func_line[512] = { '\0' };
  char loc_line[256];
  ssize_t len;
  char cmd[2048];

  // or could use global imp_argv[0] below:
  if ((len = readlink("/proc/self/exe", exe_path, sizeof(exe_path)-1)) == -1) { perror("readlink"); exit(1); }
  exe_path[len] = '\0';

  sprintf(addr_str, "0x%lx", (unsigned long)caller_addr);

  snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %s", exe_path, addr_str);
  fprintf(stderr, "Execute: %s\n", cmd);
  if (!(pipe = popen(cmd, "r"))) { perror("popen"); exit(1); }

  (void)fgets(func_line, sizeof(func_line), pipe);
  if (fgets(loc_line, sizeof(loc_line), pipe)) {
    loc_line[strcspn(loc_line, "\n")] = 0;
    fprintf(stderr, "* Unassigned variable ");
    if (func_line[0] != '\0') {
      func_line[strcspn(func_line, "\n")] = 0;
      if (strlen(func_line) > 0 && strcmp(func_line, "??") != 0) {
        fprintf(stderr, "detected in procedure %s ", func_line);
      }
    }
    fprintf(stderr, "at %s\n", loc_line);
  }
  pclose(pipe);
  exit(1);
}

static int UNASS = 0xFEFEFEFE;

static inline int __attribute__((always_inline)) _iu_inline(int v, char *vs) {
  int unass;
  if (v == unass) _imp_unass_error_str(vs);
  return v;
}

static int __attribute__ ((noinline)) _iu_no_inline(int v, char *vs) {
  int unass;
  if (v == unass) _imp_unass_error_str(vs);
  return v;
}

// Trying out different options to see how much they contribute to the code size overhead...

// U0 and U3 take 5 instructions.  U1 takes six.  U2 takes a lot more.

// U3 is probably the better choice, the size overhead is significantly reduced and
// the runtime overhead is not excessive.

#define _U3(v) _iu_no_inline(v, #v)

#define _U2(v) _iu_inline(v, #v)

#define _U1(v) ({int tmp = v; if (v == 0xFEFEFEFE) _imp_unass_error_no_str(); tmp;})

#define _U0(v) ({if (v == UNASS) _imp_unass_error_no_str(); v;})

#define _U(x) (x)

static void someproc(void) {
  int y;
  auto void nested(void) {
    int x, z;

    x = 42; z = x+1;
    fprintf(stderr, "%08x\n", x+_U3(y)+z);
    x = 17;

    x = 42; z = x+1;
    fprintf(stderr, "%08x\n", x+_U2(y)+z);
    x = 17;

    x = 42; z = x+1;
    fprintf(stderr, "%08x\n", x+_U1(y)+z);
    x = 17;
    
    x = 42; z = x+1;
    fprintf(stderr, "%08x\n", x+_U(y)+z);
    x = 17;

    x = 42; z = x+1;
    fprintf(stderr, "%08x\n", x+_U0(y)+z);
    x = 17;
  }
  nested();
}

int main(int argc, char **argv) {
  someproc();
  return 0;
}
