/* * examples_code_check.c -- Validate the code in EXAMPLES of the man pages * * Exits with a non-zero value if there is a coding error. * * Copyright (C) 2020-2023 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * * This file is part of the CoAP library libcoap. Please see README for terms * of use. */ #include #include #include #include #include #include #include #include #ifdef _WIN32 #define GCC_OPTIONS "-I../include" #ifndef WEXITSTATUS #define WEXITSTATUS(v) (v & 0xff) #endif /* WEXITSTATUS */ #else /* ! _WIN32 */ #define GCC_OPTIONS "\ -I../include \ -std=c99 \ -g \ -O2 \ -pedantic \ -Wall \ -Wcast-qual \ -Wextra \ -Wformat-security \ -Winline \ -Wmissing-declarations \ -Wmissing-prototypes \ -Wnested-externs \ -Wpointer-arith \ -Wshadow \ -Wstrict-prototypes \ -Wswitch-default \ -Wswitch-enum \ -Wunused \ -Wwrite-strings \ -Wno-unused-function \ -Wno-unused-but-set-variable \ -Werror \ " #endif /* ! _WIN32 */ const char *inline_list[] = { "coap_free(", "coap_malloc(", "coap_encode_var_bytes(", "coap_option_clrb(", "coap_option_getb(", "coap_option_setb(", "coap_read(", "coap_run_once(", "coap_write(", }; const char *define_list[] = { "coap_string_equal(", "coap_binary_equal(", "coap_log(", "coap_log_emerg(", "coap_log_alert(", "coap_log_crit(", "coap_log_err(", "coap_log_warn(", "coap_log_info(", "coap_log_notice(", "coap_log_debug(", "coap_log_oscore(", "coap_dtls_log(", }; /* xxx *function */ const char *pointer_list[] = { "char ", "coap_addr_info_t ", "coap_async_t ", "coap_attr_t ", "coap_bin_const_t ", "coap_binary_t ", "coap_cache_entry_t ", "coap_cache_key_t ", "coap_endpoint_t ", "coap_context_t ", "coap_fixed_point_t ", "coap_oscore_conf_t ", "coap_optlist_t ", "coap_session_t ", "coap_string_t ", "coap_str_const_t ", "coap_opt_iterator_t ", "coap_opt_t ", "coap_pdu_t ", "coap_resource_t ", "coap_subscription_t ", "coap_tls_version_t ", "coap_uri_t ", "const char ", "const coap_address_t ", "const coap_bin_const_t ", "const coap_pdu_t ", }; /* xxx function */ const char *number_list[] = { "coap_log_t ", "coap_pdu_type_t ", "coap_mid_t ", "coap_pdu_code_t ", "coap_proto_t ", "coap_session_state_t ", "coap_session_type_t ", "int ", "uint16_t ", "uint32_t ", "uint64_t ", "unsigned int ", "size_t ", "const uint8_t ", }; const char *return_void_list[] = { "coap_option_filter_set", "coap_resource_set_userdata", "coap_cache_set_app_data", }; int exit_code = 0; static char name_list[100][100]; static unsigned int name_cnt = 0; static char return_list[100][100]; static unsigned int return_cnt = 0; static char man_list[400][100]; static unsigned int man_cnt = 0; static void check_synopsis(const char *file) { char buffer[1024]; char file_name[300]; FILE *fpcode; int status; snprintf(file_name, sizeof(file_name), "tmp/%s-header.c", file); fpcode = fopen(file_name, "w"); if (!fpcode) { fprintf(stderr, "fopen: %s: %s (%d)\n", file_name, strerror(errno), errno); exit_code = 1; return; } fprintf(fpcode, "#include \n"); fprintf(fpcode, "#ifdef __GNUC__\n"); fprintf(fpcode, "#define U __attribute__ ((unused))\n"); fprintf(fpcode, "#else /* not a GCC */\n"); fprintf(fpcode, "#define U\n"); fprintf(fpcode, "#endif /* GCC */\n"); fprintf(fpcode, "\n"); fprintf(fpcode, "#include \"%s.h\"\n", file); fclose(fpcode); file_name[strlen(file_name)-1] = '\000'; snprintf(buffer, sizeof(buffer), "gcc " GCC_OPTIONS " -c %sc -o %so", file_name, file_name); status = system(buffer); if (WEXITSTATUS(status)) { exit_code = WEXITSTATUS(status); } return; } static void dump_name_synopsis_mismatch(void) { unsigned int i; for (i = 0; i < name_cnt; i++) { if (name_list[i][0] != '\000') { fprintf(stderr, "NAME: %s not in SYNOPSIS or pointer_list[]\n", name_list[i]); exit_code = 1; } } name_cnt = 0; } static void dump_return_synopsis_mismatch(void) { unsigned int i; for (i = 0; i < return_cnt; i++) { if (return_list[i][0] != '\000') { fprintf(stderr, "SYNOPSIS: %s not in RETURN VALUES\n", return_list[i]); exit_code = 1; } } return_cnt = 0; } /* * It is assumed for a definition that a leading * and trailing * have been * added to buffer. */ static void decode_synopsis_definition(FILE *fpheader, const char *buffer, int in_synopsis) { size_t len; char outbuf[1024]; const char *cp; const char *ecp; int is_void_func = 0; int is_number_func = 0; int is_inline_func = 0; int is_struct_func = 0; const char *func_start = NULL; int is_struct = 0; unsigned int i; if (strncmp(buffer, "*void ", sizeof("*void ")-1) == 0) { if (strncmp(buffer, "*void *", sizeof("*void *")-1) == 0) { func_start = &buffer[sizeof("*void *")-1]; } else { is_void_func = 1; func_start = &buffer[sizeof("*void ")-1]; } } for (i = 0; i < sizeof(number_list)/sizeof(number_list[0]); i++) { if (strncmp(&buffer[1], number_list[i], strlen(number_list[i])) == 0) { if (buffer[1 + strlen(number_list[i])] == '*') { func_start = &buffer[2 + strlen(number_list[i])]; } else { is_number_func = 1; func_start = &buffer[1 + strlen(number_list[i])]; } break; } } for (i = 0; i < sizeof(pointer_list)/sizeof(pointer_list[0]); i++) { if (strncmp(&buffer[1], pointer_list[i], strlen(pointer_list[i])) == 0) { if (buffer[1 + strlen(pointer_list[i])] == '*') { func_start = &buffer[2 + strlen(pointer_list[i])]; } else { is_struct_func = i + 1; func_start = &buffer[1 + strlen(pointer_list[i])]; } break; } } if (strncmp(buffer, "*struct ", sizeof("*struct ")-1) == 0) { is_struct = 1; func_start = &buffer[sizeof("*struct ")-1]; } if (func_start) { /* see if COAP_STATIC_INLINE function */ for (i = 0; i < sizeof(inline_list)/sizeof(inline_list[0]); i++) { if (strncmp(func_start, inline_list[i], strlen(inline_list[i])) == 0) { is_inline_func = 1; break; } } /* see if #define function */ for (i = 0; i < sizeof(define_list)/sizeof(define_list[0]); i++) { if (strncmp(func_start, define_list[i], strlen(define_list[i])) == 0) { break; } } if (i != sizeof(define_list)/sizeof(define_list[0])) goto cleanup; } /* Need to include use of U for unused parameters just before comma */ cp = buffer; ecp = strchr(cp, ','); if (!ecp) ecp = strchr(cp, ')'); outbuf[0] = '\000'; while (ecp) { len = strlen(outbuf); if (strncmp(cp, "void", ecp-cp) == 0) snprintf(&outbuf[len], sizeof(outbuf)-len, "%*.*s%c", (int)(ecp-cp), (int)(ecp-cp), cp, *ecp); else snprintf(&outbuf[len], sizeof(outbuf)-len, "%*.*s U%c", (int)(ecp-cp), (int)(ecp-cp), cp, *ecp); cp = ecp+1; if (*cp) { ecp = strchr(cp, ','); if (!ecp) ecp = strchr(cp, ')'); } else { ecp = NULL; } } if (*cp) { len = strlen(outbuf); snprintf(&outbuf[len], sizeof(outbuf)-len, "%s", cp); } len = strlen(outbuf); if (len > 3 && ((outbuf[len-3] == ';' && outbuf[len-2] == '*') || (outbuf[len-3] == '*' && outbuf[len-2] == ';'))) { if (is_inline_func) { strcpy(&outbuf[len-3], ";\n"); } /* Replace ;* or *; with simple function definition */ else if (is_void_func) { strcpy(&outbuf[len-3], "{}\n"); } else if (is_number_func) { strcpy(&outbuf[len-3], "{return 0;}\n"); } else if (is_struct_func) { snprintf(&outbuf[len-3], sizeof(outbuf)-(len-3), "{%s v; memset(&v, 0, sizeof(v)); return v;}\n", pointer_list[is_struct_func - 1]); } else if (is_struct) { strcpy(&outbuf[len-3], ";\n"); } else { strcpy(&outbuf[len-3], "{return NULL;}\n"); } } if (outbuf[0] == '*') { fprintf(fpheader, "%s", &outbuf[1]); } else { fprintf(fpheader, "%s", outbuf); } cleanup: if (in_synopsis && func_start) { char *wcp = strchr(func_start, '('); if (!wcp && is_struct) wcp = strchr(func_start, ';'); if (!wcp) { wcp = strchr(func_start, '\n'); if (wcp) *wcp = '\000'; fprintf(stderr, "SYNOPSIS: function %s issue\n", func_start); return; } *wcp = '\000'; for (i = 0; i < name_cnt; i++) { if (strcmp(name_list[i], func_start) == 0) { name_list[i][0] = '\000'; break; } } if (i == name_cnt) { fprintf(stderr, "SYNOPSIS: %s not in NAME\n", func_start); exit_code = 1; } if (!is_void_func && !is_struct) { for (i = 0; i < return_cnt; i++) { if (strcmp(func_start, return_list[i]) == 0) { fprintf(stderr, "SYNOPSIS: %s duplicated\n", func_start); break; } } if (i != return_cnt) return; if (i >= (int)(sizeof(return_list)/sizeof(return_list[0]))) { fprintf(stderr, "SYNOPSIS: %s insufficient space (%u >= %u)\n", func_start, i, (int)(sizeof(return_list)/sizeof(return_list[0]))); return; } strncpy(return_list[i], func_start, sizeof(return_list[i])-1); return_cnt++; } } return; } int main(int argc, char *argv[]) { DIR *pdir; struct dirent *pdir_ent; char buffer[1024]; char man_name[1024]; int status; size_t i; int man_missing_first = 1; if (argc != 2 && argc != 3) { fprintf(stderr, "usage: %s man_directory [libcoap-3.sym_file]\n", argv[0]); exit(1); } pdir = opendir(argv[1]); if (pdir == NULL) { fprintf(stderr, "opendir: %s: %s (%d)\n", argv[1], strerror(errno), errno); exit(1); } if (argc == 3) { FILE *fp = fopen(argv[2], "r"); char tmp_name[100]; if (fp == NULL) { fprintf(stderr, "fopen: %s: %s (%d)\n", argv[2], strerror(errno), errno); exit(1); } while (fgets(tmp_name, sizeof(tmp_name), fp) != NULL) { char *cp = strchr(tmp_name, '\n'); if (cp) *cp = '\000'; if (tmp_name[0]) { strncpy(man_list[man_cnt], tmp_name, sizeof(man_list[man_cnt])); man_cnt++; } if (man_cnt == sizeof(man_list) / sizeof(name_list[0])) { fprintf(stderr, "man_list[] too small (%zd) for entries from %s\n", sizeof(man_list) / sizeof(name_list[0]), argv[2]); exit(1); } } fclose(fp); } if (chdir(argv[1]) == -1) { fprintf(stderr, "chdir: %s: %s (%d)\n", argv[1], strerror(errno), errno); exit(1); } #if defined(WIN32) || defined(__MINGW32__) if (mkdir("tmp") == -1 && errno != EEXIST) { fprintf(stderr, "mkdir: %s: %s (%d)\n", "tmp", strerror(errno), errno); exit(1); } #else /* ! WIN32 && ! __MINGW32__ */ if (mkdir("tmp", 0777) == -1 && errno != EEXIST) { fprintf(stderr, "mkdir: %s: %s (%d)\n", "tmp", strerror(errno), errno); exit(1); } #endif /* ! WIN32 && ! __MINGW32__ */ while ((pdir_ent = readdir(pdir)) != NULL) { if (!strncmp(pdir_ent->d_name, "coap_", sizeof("coap_")-1) && strstr(pdir_ent->d_name, ".txt.in")) { FILE *fp; int skip = 1; int in_examples = 0; int in_synopsis = 0; int in_functions = 0; int in_name = 0; int in_return = 0; int count = 1; char keep_line[1024] = {0}; FILE *fpcode = NULL; FILE *fpheader = NULL; char file_name[300]; char *cp; fprintf(stderr, "Processing: %s\n", pdir_ent->d_name); snprintf(man_name, sizeof(man_name), "%s", pdir_ent->d_name); fp = fopen(man_name, "r"); if (fp == NULL) { fprintf(stderr, "fopen: %s: %s (%d)\n", man_name, strerror(errno), errno); continue; } cp = strstr(man_name, ".txt.in"); if (cp) *cp = '\000'; name_cnt = 0; while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strncmp(buffer, "NAME", sizeof("NAME")-1) == 0) { in_name = 1; continue; } if (strncmp(buffer, "SYNOPSIS", sizeof("SYNOPSIS")-1) == 0) { in_name = 0; in_synopsis = 1; snprintf(file_name, sizeof(file_name), "tmp/%s.h", pdir_ent->d_name); fpheader = fopen(file_name, "w"); if (!fpheader) { fprintf(stderr, "fopen: %s: %s (%d)\n", file_name, strerror(errno), errno); goto bad; } continue; } if (strncmp(buffer, "FUNCTIONS", sizeof("FUNCTIONS")-1) == 0) { in_synopsis = 0; dump_name_synopsis_mismatch(); in_functions = 1; snprintf(file_name, sizeof(file_name), "tmp/%s.h", pdir_ent->d_name); fpheader = fopen(file_name, "w"); if (!fpheader) { fprintf(stderr, "fopen: %s: %s (%d)\n", file_name, strerror(errno), errno); goto bad; } continue; } if (strncmp(buffer, "DESCRIPTION", sizeof("DESCRIPTION")-1) == 0) { in_synopsis = 0; dump_name_synopsis_mismatch(); if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); continue; } if (strncmp(buffer, "RETURN VALUES", sizeof("RETURN VALUES")-1) == 0 || strncmp(buffer, "RETURN VALUE", sizeof("RETURN VALUE")-1) == 0) { if (in_functions) { if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); } in_name = 0; in_synopsis = 0; dump_name_synopsis_mismatch(); in_functions = 0; in_return = 1; continue; } if (strncmp(buffer, "TESTING", sizeof("TESTING")-1) == 0) { if (in_functions) { if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); } in_name = 0; in_synopsis = 0; in_functions = 0; in_return = 0; continue; } if (strncmp(buffer, "EXAMPLES", sizeof("EXAMPLES")-1) == 0 || strncmp(buffer, "EXAMPLE", sizeof("EXAMPLE")-1) == 0) { if (in_functions) { if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); } in_name = 0; in_synopsis = 0; in_return = 0; dump_name_synopsis_mismatch(); dump_return_synopsis_mismatch(); in_functions = 0; in_examples = 1; continue; } if (strncmp(buffer, "SEE ALSO", sizeof("SEE ALSO")-1) == 0) { if (in_functions) { if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); } in_name = 0; in_synopsis = 0; in_return = 0; dump_name_synopsis_mismatch(); dump_return_synopsis_mismatch(); in_functions = 0; in_examples = 1; break; } if (in_name) { /* Working in NAME section */ if (buffer[0] == '\n') continue; if (buffer[0] == '-') continue; cp = strchr(buffer, '\n'); if (cp) *cp = '\000'; cp = strchr(buffer, ','); if (cp) *cp = '\000'; if (strcmp(man_name, buffer) == 0) continue; if (strlen(buffer) >= sizeof(name_list[0])) { fprintf(stderr, "NAME: %s is too long (%u >= %u)\n", buffer, (int)strlen(buffer), (int)sizeof(name_list[0])); continue; } for (i = 0; i < name_cnt; i++) { if (strncmp(buffer, name_list[i], sizeof(name_list[i])) == 0) { fprintf(stderr, "NAME: %s duplicated\n", buffer); break; } } if (i != name_cnt) continue; if (i >= (int)(sizeof(name_list)/sizeof(name_list[0]))) { fprintf(stderr, "NAME: %s insufficient space (%zu >= %u)\n", buffer, i, (int)(sizeof(name_list)/sizeof(name_list[0]))); continue; } memcpy(name_list[i], buffer, sizeof(name_list[i])-1); name_list[i][sizeof(name_list[i])-1] = '\000'; name_cnt++; for (i = 0; i < man_cnt; i++) { if (strncmp(man_list[i], buffer, sizeof(man_list[i])) == 0) { man_list[i][0] = '\000'; break; } } } if (in_synopsis) { /* Working in SYNOPSIS section */ size_t len; char outbuf[1024]; if (buffer[0] == '*' && buffer[1] != '#') { /* Start of a new entry */ snprintf(outbuf, sizeof(outbuf), "%s", buffer); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (buffer[0] == '\n') { break; } len = strlen(outbuf); outbuf[len-1] = ' '; snprintf(&outbuf[len], sizeof(outbuf) - len, "%s", buffer); } decode_synopsis_definition(fpheader, outbuf, 1); continue; } if (buffer[0] == '\n') continue; if (buffer[0] == '-') continue; if (buffer[0] == 'F') { /* For specific ..... is the end */ in_synopsis = 0; if (fpheader) fclose(fpheader); fpheader = NULL; check_synopsis(pdir_ent->d_name); continue; } } if (in_functions) { /* Working in FUNCTIONS section */ size_t len; char outbuf[1024]; if (strncmp(buffer, "Prototype:\n", sizeof("Prototype:\n")-1)== 0) { /* Start of a new entry */ outbuf[0] = '*'; outbuf[1] = '\000'; while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (buffer[0] == '\n') { break; } len = strlen(outbuf); if (outbuf[len-1] == '\n') outbuf[len-1] = ' '; snprintf(&outbuf[len], sizeof(outbuf) - len, "%s", buffer); } len = strlen(outbuf); len--; snprintf(&outbuf[len], sizeof(outbuf) - len, "*\n"); decode_synopsis_definition(fpheader, outbuf, 0); continue; } } if (in_return) { cp = strstr(buffer, "*coap_"); while (cp) { char *ecp = strchr(cp+1, '*'); if (!ecp) { fprintf(stderr, "RETURN VALUES: %s undecipherable\n", cp + 1); exit_code = 1; break; } *ecp = '\000'; for (i = 0; i < return_cnt; i++) { if (strcmp(cp+1, return_list[i]) == 0) { return_list[i][0] = '\000'; break; } } if (i == return_cnt) { for (i = 0; i < sizeof(return_void_list)/sizeof(return_void_list[0]); i++) { if (strcmp(cp+1, return_void_list[i]) == 0) break; } if (i == sizeof(return_void_list)/sizeof(return_void_list[0])) { fprintf(stderr, "RETURN VALUES: %s not defined in SYNOPSIS\n", cp + 1); exit_code = 1; } } cp = strstr(ecp+1, "*coap_"); } } if (!in_examples) { continue; } /* Working in EXAMPLES section */ if (skip) { if (!strcmp(buffer, "----\n") || !strcmp(buffer, "---\n") || !strcmp(buffer, "--\n") || !strcmp(buffer, "-\n") || !strcmp(buffer, "-----\n")) { /* Found start of code */ if (strcmp(buffer, "----\n")) { fprintf(stderr, "Unexpected start of code '%.*s' - expected ----\n", (int)strlen(buffer)-1, buffer); } snprintf(file_name, sizeof(file_name), "tmp/%s-%d.c", pdir_ent->d_name, count++); fpcode = fopen(file_name, "w"); if (!fpcode) { fprintf(stderr, "fopen: %s: %s (%d)\n", file_name, strerror(errno), errno); goto bad; } else { fprintf(fpcode, "/* %s */\n", keep_line); } skip = 0; fprintf(stderr, "Processing: %s EXAMPLE - '%d'\n", pdir_ent->d_name, count-1); } else if (buffer[0] == '*') { snprintf(keep_line, sizeof(keep_line), "%s", buffer); } continue; } if (!strcmp(buffer, "----\n") || !strcmp(buffer, "---\n") || !strcmp(buffer, "--\n") || !strcmp(buffer, "-\n") || !strcmp(buffer, "-----\n")) { /* Found end of code */ skip = 1; if (fpcode) fclose(fpcode); keep_line[0] = '\000'; file_name[strlen(file_name)-1] = '\000'; snprintf(buffer, sizeof(buffer), "gcc " GCC_OPTIONS " -c %sc -o %so", file_name, file_name); status = system(buffer); if (WEXITSTATUS(status)) { exit_code = WEXITSTATUS(status); } continue; } if (fpcode) { if (strstr(buffer, "LIBCOAP_API_VERSION")) { fprintf(fpcode, "#include \n"); fprintf(fpcode, "#ifdef __GNUC__\n"); fprintf(fpcode, "#define U __attribute__ ((unused))\n"); fprintf(fpcode, "#else /* not a GCC */\n"); fprintf(fpcode, "#define U\n"); fprintf(fpcode, "#endif /* GCC */\n"); fprintf(fpcode, "#include \"%s.h\"\n", pdir_ent->d_name); fprintf(fpcode, "#undef U\n"); continue; } fprintf(fpcode, "%s", buffer); } } bad: fclose(fp); } } closedir(pdir); for (i = 0; i < man_cnt; i++) { if (man_list[i][0]) { if (man_missing_first) { man_missing_first = 0; fprintf(stderr, "\nMissing man pages (for reference only)\n"); } fprintf(stderr, "%s\n", man_list[i]); } } exit(exit_code); }