nuttx/tools/nxstyle.c
Xiang Xiao 59db30fd59 tools/nxstyle: Add __sFILE_fake(defined by newlib) to the white list
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2022-05-06 14:43:57 -03:00

3030 lines
94 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/********************************************************************************
* tools/nxstyle.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
********************************************************************************/
/********************************************************************************
* Included Files
********************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <unistd.h>
#include <libgen.h>
/********************************************************************************
* Pre-processor Definitions
********************************************************************************/
#define NXSTYLE_VERSION "0.01"
#define LINE_SIZE 512
#define RANGE_NUMBER 4096
#define DEFAULT_WIDTH 78
#define FIRST_SECTION INCLUDED_FILES
#define LAST_SECTION PUBLIC_FUNCTION_PROTOTYPES
#define FATAL(m, l, o) message(FATAL, (m), (l), (o))
#define FATALFL(m, s) message(FATAL, (m), -1, -1)
#define WARN(m, l, o) message(WARN, (m), (l), (o))
#define ERROR(m, l, o) message(ERROR, (m), (l), (o))
#define ERRORFL(m, s) message(ERROR, (m), -1, -1)
#define INFO(m, l, o) message(INFO, (m), (l), (o))
#define INFOFL(m, s) message(INFO, (m), -1, -1)
/********************************************************************************
* Private types
********************************************************************************/
enum class_e
{
INFO,
WARN,
ERROR,
FATAL
};
const char *class_text[] =
{
"info",
"warning",
"error",
"fatal"
};
enum file_e
{
UNKNOWN = 0x00,
C_HEADER = 0x01,
C_SOURCE = 0x02
};
enum section_s
{
NO_SECTION = 0,
INCLUDED_FILES,
PRE_PROCESSOR_DEFINITIONS,
PUBLIC_TYPES,
PRIVATE_TYPES,
PRIVATE_DATA,
PUBLIC_DATA,
PRIVATE_FUNCTIONS,
PRIVATE_FUNCTION_PROTOTYPES,
INLINE_FUNCTIONS,
PUBLIC_FUNCTIONS,
PUBLIC_FUNCTION_PROTOTYPES
};
enum pptype_e
{
PPLINE_NONE = 0,
PPLINE_DEFINE,
PPLINE_IF,
PPLINE_ELIF,
PPLINE_ELSE,
PPLINE_ENDIF,
PPLINE_OTHER
};
struct file_section_s
{
const char *name; /* File section name */
uint8_t ftype; /* File type where section found */
};
/********************************************************************************
* Private data
********************************************************************************/
static enum file_e g_file_type = UNKNOWN;
static enum section_s g_section = NO_SECTION;
static int g_maxline = DEFAULT_WIDTH;
static int g_status = 0;
static int g_verbose = 2;
static int g_rangenumber = 0;
static int g_rangestart[RANGE_NUMBER];
static int g_rangecount[RANGE_NUMBER];
static char g_file_name[PATH_MAX];
static const struct file_section_s g_section_info[] =
{
{
" *\n", /* Index: NO_SECTION */
C_SOURCE | C_HEADER
},
{
" * Included Files\n", /* Index: INCLUDED_FILES */
C_SOURCE | C_HEADER
},
{
" * Pre-processor Definitions\n", /* Index: PRE_PROCESSOR_DEFINITIONS */
C_SOURCE | C_HEADER
},
{
" * Public Types\n", /* Index: PUBLIC_TYPES */
C_HEADER
},
{
" * Private Types\n", /* Index: PRIVATE_TYPES */
C_SOURCE
},
{
" * Private Data\n", /* Index: PRIVATE_DATA */
C_SOURCE
},
{
" * Public Data\n", /* Index: PUBLIC_DATA */
C_SOURCE | C_HEADER
},
{
" * Private Functions\n", /* Index: PRIVATE_FUNCTIONS */
C_SOURCE
},
{
" * Private Function Prototypes\n", /* Index: PRIVATE_FUNCTION_PROTOTYPES */
C_SOURCE
},
{
" * Inline Functions\n", /* Index: INLINE_FUNCTIONS */
C_SOURCE | C_HEADER
},
{
" * Public Functions\n", /* Index: PUBLIC_FUNCTIONS */
C_SOURCE
},
{
" * Public Function Prototypes\n", /* Index: PUBLIC_FUNCTION_PROTOTYPES */
C_SOURCE | C_HEADER
}
};
static const char *g_white_prefix[] =
{
"ASCII_", /* Ref: include/nuttx/ascii.h */
"Elf", /* Ref: include/elf.h, include/elf32.h, include/elf64.h */
"PRId", /* Ref: inttypes.h */
"PRIi", /* Ref: inttypes.h */
"PRIo", /* Ref: inttypes.h */
"PRIu", /* Ref: inttypes.h */
"PRIx", /* Ref: inttypes.h */
"SCNd", /* Ref: inttypes.h */
"SCNi", /* Ref: inttypes.h */
"SCNo", /* Ref: inttypes.h */
"SCNu", /* Ref: inttypes.h */
"SCNx", /* Ref: inttypes.h */
"SYS_", /* Ref: include/sys/syscall.h */
"STUB_", /* Ref: syscall/syscall_lookup.h, syscall/sycall_stublookup.c */
"XK_", /* Ref: include/input/X11_keysymdef.h */
"b8", /* Ref: include/fixedmath.h */
"b16", /* Ref: include/fixedmath.h */
"b32", /* Ref: include/fixedmath.h */
"ub8", /* Ref: include/fixedmath.h */
"ub16", /* Ref: include/fixedmath.h */
"ub32", /* Ref: include/fixedmath.h */
"lua_", /* Ref: apps/interpreters/lua/lua-5.x.x/src/lua.h */
"luaL_", /* Ref: apps/interpreters/lua/lua-5.x.x/src/lauxlib.h */
NULL
};
static const char *g_white_list[] =
{
/* Ref: gnu_unwind_find_exidx.c */
"__EIT_entry",
/* Ref: gnu_unwind_find_exidx.c */
"__gnu_Unwind_Find_exidx",
/* Ref: lib_impure.c */
"__sFILE_fake",
/* Ref: stdlib.h */
"_Exit",
/* Ref: stdatomic.h */
"_Atomic",
/* Ref: unwind-arm-common.h */
"_Unwind",
/* Ref:
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html
*/
"P_tmpdir",
/* Ref:
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html
*/
"L_tmpnam",
/* Ref:
* nuttx/compiler.h
*/
"_Far",
"_Erom",
/* Ref:
* arch/sim/src/sim/up_wpcap.c
*/
"Address",
"Description",
"FirstUnicastAddress",
"GetAdaptersAddresses",
"GetProcAddress",
"LoadLibrary",
"lpSockaddr",
"Next",
"PhysicalAddressLength",
"PhysicalAddress",
"WideCharToMultiByte",
/* Ref:
* drivers/segger/note_sysview.c
*/
"SEGGER_SYSVIEW",
"TaskID",
"sName",
"Prio",
"StackBase",
"StackSize",
/* Ref:
* drivers/segger/syslog_rtt.c
*/
"SEGGER_RTT",
/* Ref:
* fs/nfs/rpc.h
* fs/nfs/nfs_proto.h
* fs/nfs/nfs_mount.h
* fs/nfs/nfs_vfsops.c
*/
"CREATE3args",
"CREATE3resok",
"LOOKUP3args",
"LOOKUP3filename",
"LOOKUP3resok",
"WRITE3args",
"WRITE3resok",
"READ3args",
"READ3resok",
"REMOVE3args",
"REMOVE3resok",
"RENAME3args",
"RENAME3resok",
"MKDIR3args",
"MKDIR3resok",
"RMDIR3args",
"RMDIR3resok",
"READDIR3args",
"READDIR3resok",
"SETATTR3args",
"SETATTR3resok",
"FS3args",
"SIZEOF_rpc_reply_read",
"SIZEOF_rpc_call_write",
"SIZEOF_rpc_reply_readdir",
"SIZEOF_nfsmount",
/* Ref:
* mm/kasan/kasan.c
*/
"__asan_loadN",
"__asan_storeN",
"__asan_loadN_noabort",
"__asan_storeN_noabort",
/* Ref:
* tools/jlink-nuttx.c
*/
"RTOS_Init",
"RTOS_GetVersion",
"RTOS_GetSymbols",
"RTOS_GetNumThreads",
"RTOS_GetCurrentThreadId",
"RTOS_GetThreadId",
"RTOS_GetThreadDisplay",
"RTOS_GetThreadReg",
"RTOS_GetThreadRegList",
"RTOS_GetThreadRegList",
"RTOS_SetThreadReg",
"RTOS_SetThreadRegList",
"RTOS_UpdateThreads",
NULL
};
/********************************************************************************
* Private Functions
********************************************************************************/
/********************************************************************************
* Name: show_usage
*
* Description:
*
********************************************************************************/
static void show_usage(char *progname, int exitcode, char *what)
{
fprintf(stderr, "%s version %s\n\n", basename(progname), NXSTYLE_VERSION);
if (what)
{
fprintf(stderr, "%s\n", what);
}
fprintf(stderr, "Usage: %s [-m <excess>] [-v <level>] "
"[-r <start,count>] <filename>\n",
basename(progname));
fprintf(stderr, " %s -h this help\n", basename(progname));
fprintf(stderr, " %s -v <level> where level is\n",
basename(progname));
fprintf(stderr, " 0 - no output\n");
fprintf(stderr, " 1 - PASS/FAIL\n");
fprintf(stderr, " 2 - output each line (default)\n");
exit(exitcode);
}
/********************************************************************************
* Name: skip
*
* Description:
*
********************************************************************************/
static int skip(int lineno)
{
int i;
for (i = 0; i < g_rangenumber; i++)
{
if (lineno >= g_rangestart[i] && lineno < g_rangestart[i] +
g_rangecount[i])
{
return 0;
}
}
return g_rangenumber != 0;
}
/********************************************************************************
* Name: message
*
* Description:
*
********************************************************************************/
static int message(enum class_e class, const char *text, int lineno, int ndx)
{
FILE *out = stdout;
if (skip(lineno))
{
return g_status;
}
if (class > INFO)
{
out = stderr;
g_status |= 1;
}
if (g_verbose == 2)
{
if (lineno == -1 && ndx == -1)
{
fprintf(out, "%s: %s: %s\n", g_file_name, class_text[class], text);
}
else
{
fprintf(out, "%s:%d:%d: %s: %s\n", g_file_name, lineno, ndx,
class_text[class], text);
}
}
return g_status;
}
/********************************************************************************
* Name: check_spaces_left
*
* Description:
*
********************************************************************************/
static void check_spaces_left(char *line, int lineno, int ndx)
{
/* Unary operator should generally be preceded by a space but make also
* follow a left parenthesis at the beginning of a parenthetical list or
* expression or follow a right parentheses in the case of a cast.
*/
if (ndx-- > 0 && line[ndx] != ' ' && line[ndx] != '(' && line[ndx] != ')')
{
ERROR("Operator/assignment must be preceded with whitespace",
lineno, ndx);
}
}
/********************************************************************************
* Name: check_spaces_leftright
*
* Description:
*
********************************************************************************/
static void check_spaces_leftright(char *line, int lineno, int ndx1, int ndx2)
{
if (ndx1 > 0 && line[ndx1 - 1] != ' ')
{
ERROR("Operator/assignment must be preceded with whitespace",
lineno, ndx1);
}
if (line[ndx2 + 1] != '\0' && line[ndx2 + 1] != '\n' && line[ndx2 + 1] != ' ')
{
ERROR("Operator/assignment must be followed with whitespace",
lineno, ndx2);
}
}
/********************************************************************************
* Name: check_nospaces_leftright
*
* Description:
* Check if there are whitespaces on the left of right. If there is, report
* an error.
*
********************************************************************************/
static void check_nospaces_leftright(char *line, int lineno, int ndx1, int ndx2)
{
if (ndx1 > 0 && line[ndx1 - 1] == ' ')
{
ERROR("There should be no spaces before the operator/assignment",
lineno, ndx1);
}
if (line[ndx2 + 1] == ' ')
{
ERROR("There should be no spaces after the operator/assignment",
lineno, ndx2);
}
}
/********************************************************************************
* Name: check_operand_leftright
*
* Description:
* Check if the operator is next to an operand. If not, report the error.
*
********************************************************************************/
static void check_operand_leftright(char *line, int lineno, int ndx1, int ndx2)
{
/* The cases below includes("xx" represents the operator):
* " xx " | " xx(end)" | " xx;" | " xx\n" | " xx)" | " xx]" - (ndx1 > 0)
* "(xx " | "(xx(end)" | "(xx;" | "(xx\n" | "(xx)" | "(xx]" - (ndx1 > 0)
* "[xx " | "[xx(end)" | "[xx;" | "[xx\n" | "[xx)" | "[xx]" - (ndx1 > 0)
* "xx " | "xx(end)" | "xx;" | "xx\n" | "xx)" | "xx]" - (ndx1 = 0)
* In these cases, the operators must be not next any operands, thus errors
* are reported.
*/
if (ndx1 > 0 && (line[ndx1 - 1] == ' ' || line[ndx1 - 1] == '(' ||
line[ndx1 - 1] == '[') &&
(line[ndx2 + 1] == ' ' || line[ndx2 + 1] == '\0' ||
line[ndx2 + 1] == ';' || line[ndx2 + 1] == '\n' ||
line[ndx2 + 1] == ')' || line[ndx2 + 1] == ']'))
{
ERROR("Operator must be next to an operand", lineno, ndx2);
}
}
/********************************************************************************
* Name: block_comment_width
*
* Description:
* Get the width of a block comment
*
********************************************************************************/
static int block_comment_width(char *line)
{
int b;
int e;
int n;
/* Skip over any leading whitespace on the line */
for (b = 0; isspace(line[b]); b++)
{
}
/* Skip over any trailing whitespace at the end of the line */
for (e = strlen(line) - 1; e >= 0 && isspace(line[e]); e--)
{
}
/* Number of characters on the line */
n = e - b + 1;
if (n < 4)
{
return 0;
}
/* The first line of a block comment starts with "[slash]***" and ends with
* "***"
*/
if (strncmp(&line[b], "/***", 4) == 0 &&
strncmp(&line[e - 2], "***", 3) == 0)
{
/* Return the the length of the line up to the final '*' */
return e + 1;
}
/* The last line of a block begins with whitespace then "***" and ends
* with "***[slash]"
*/
if (strncmp(&line[b], "***", 3) == 0 &&
strncmp(&line[e - 3], "***/", 4) == 0)
{
/* Return the the length of the line up to the final '*' */
return e;
}
/* But there is also a special single line comment that begins with "[slash]* "
* and ends with "***[slash]"
*/
if (strncmp(&line[b], "/*", 2) == 0 &&
strncmp(&line[e - 3], "***/", 4) == 0)
{
/* Return the the length of the line up to the final '*' */
return e;
}
/* Return zero if the line is not the first or last line of a block
* comment.
*/
return 0;
}
/********************************************************************************
* Name: get_line_width
*
* Description:
* Get the maximum line width by examining the width of the block comments.
*
********************************************************************************/
static int get_line_width(FILE *instream)
{
char line[LINE_SIZE]; /* The current line being examined */
int max = 0;
int min = INT_MAX;
int lineno = 0;
int lineno_max = 0;
int lineno_min = 0;
int len;
while (fgets(line, LINE_SIZE, instream))
{
lineno++;
len = block_comment_width(line);
if (len > 0)
{
if (len > max)
{
max = len;
lineno_max = lineno;
}
if (len < min)
{
min = len;
lineno_min = lineno;
}
}
}
if (max < min)
{
ERRORFL("No block comments found", g_file_name);
return DEFAULT_WIDTH;
}
else if (max != min)
{
ERROR("Block comments have different lengths", lineno_max, max);
ERROR("Block comments have different lengths", lineno_min, min);
return DEFAULT_WIDTH;
}
return min;
}
/********************************************************************************
* Name: check_section_header
*
* Description:
* Check if the current line holds a section header
*
********************************************************************************/
static bool check_section_header(const char *line, int lineno)
{
int i;
/* Search g_section_info[] to find a matching section header line */
for (i = FIRST_SECTION; i <= LAST_SECTION; i++)
{
if (strcmp(line, g_section_info[i].name) == 0)
{
g_section = (enum section_s)i;
/* Verify that this section is appropriate for this file type */
if ((g_file_type & g_section_info[i].ftype) == 0)
{
ERROR("Invalid section for this file type", lineno, 3);
}
return true;
}
}
return false;
}
/********************************************************************************
* Name: white_prefix
*
* Description:
* Return true if the identifier string begins with a white-listed prefix
*
********************************************************************************/
static bool white_list(const char *ident, int lineno)
{
const char **pptr;
const char *str;
for (pptr = g_white_prefix;
(str = *pptr) != NULL;
pptr++)
{
if (strncmp(ident, str, strlen(str)) == 0)
{
return true;
}
}
for (pptr = g_white_list;
(str = *pptr) != NULL;
pptr++)
{
size_t len = strlen(str);
if (strncmp(ident, str, len) == 0 &&
isalnum(ident[len]) == 0)
{
return true;
}
}
return false;
}
/********************************************************************************
* Public Functions
********************************************************************************/
int main(int argc, char **argv, char **envp)
{
FILE *instream; /* File input stream */
char line[LINE_SIZE]; /* The current line being examined */
char buffer[100]; /* Localy format error strings */
char *lptr; /* Temporary pointer into line[] */
char *ext; /* Temporary file extension */
bool btabs; /* True: TAB characters found on the line */
bool bcrs; /* True: Carriage return found on the line */
bool bfunctions; /* True: In private or public functions */
bool bstatm; /* True: This line is beginning of a statement */
bool bfor; /* True: This line is beginning of a 'for' statement */
bool bswitch; /* True: Within a switch statement */
bool bstring; /* True: Within a string */
bool bquote; /* True: Backslash quoted character next */
bool bblank; /* Used to verify block comment terminator */
bool bexternc; /* True: Within 'extern "C"' */
enum pptype_e ppline; /* > 0: The next line the continuation of a
* pre-processor command */
int rhcomment; /* Indentation of Comment to the right of code
* (-1 -> don't check position) */
int prevrhcmt; /* Indentation of previous Comment to the right
* of code (-1 -> don't check position) */
int lineno; /* Current line number */
int indent; /* Indentation level */
int ncomment; /* Comment nesting level on this line */
int prevncomment; /* Comment nesting level on the previous line */
int bnest; /* Brace nesting level on this line */
int prevbnest; /* Brace nesting level on the previous line */
int dnest; /* Data declaration nesting level on this line */
int prevdnest; /* Data declaration nesting level on the previous line */
int pnest; /* Parenthesis nesting level on this line */
int ppifnest; /* #if nesting level on this line */
int inasm; /* > 0: Within #ifdef __ASSEMBLY__ */
int comment_lineno; /* Line on which the last comment was closed */
int blank_lineno; /* Line number of the last blank line */
int noblank_lineno; /* A blank line is not needed after this line */
int lbrace_lineno; /* Line number of last left brace */
int rbrace_lineno; /* Last line containing a right brace */
int externc_lineno; /* Last line where 'extern "C"' declared */
int linelen; /* Length of the line */
int excess;
int n;
int i;
int c;
excess = 0;
while ((c = getopt(argc, argv, ":hv:gm:r:")) != -1)
{
switch (c)
{
case 'm':
excess = atoi(optarg);
if (excess < 1)
{
show_usage(argv[0], 1, "Bad value for <excess>.");
excess = 0;
}
break;
case 'v':
g_verbose = atoi(optarg);
if (g_verbose < 0 || g_verbose > 2)
{
show_usage(argv[0], 1, "Bad value for <level>.");
}
break;
case 'r':
g_rangestart[g_rangenumber] = atoi(strtok(optarg, ","));
g_rangecount[g_rangenumber++] = atoi(strtok(NULL, ","));
break;
case 'h':
show_usage(argv[0], 0, NULL);
break;
case ':':
show_usage(argv[0], 1, "Missing argument.");
break;
case '?':
show_usage(argv[0], 1, "Unrecognized option.");
break;
default:
show_usage(argv[0], 0, NULL);
break;
}
}
if (optind < argc - 1 || argv[optind] == NULL)
{
show_usage(argv[0], 1, "No file name given.");
}
/* Resolve the absolute path for the input file */
if (realpath(argv[optind], g_file_name) == NULL)
{
FATALFL("Failed to resolve absolute path.", g_file_name);
return 1;
}
/* Are we parsing a header file? */
ext = strrchr(g_file_name, '.');
if (ext == 0)
{
}
else if (strcmp(ext, ".h") == 0)
{
g_file_type = C_HEADER;
}
else if (strcmp(ext, ".c") == 0)
{
g_file_type = C_SOURCE;
}
if (g_file_type == UNKNOWN)
{
return 0;
}
instream = fopen(g_file_name, "r");
if (!instream)
{
FATALFL("Failed to open", g_file_name);
return 1;
}
/* Determine the line width */
g_maxline = get_line_width(instream) + excess;
rewind(instream);
btabs = false; /* True: TAB characters found on the line */
bcrs = false; /* True: Carriage return found on the line */
bfunctions = false; /* True: In private or public functions */
bswitch = false; /* True: Within a switch statement */
bstring = false; /* True: Within a string */
bexternc = false; /* True: Within 'extern "C"' */
ppline = PPLINE_NONE; /* > 0: The next line the continuation of a
* pre-processor command */
rhcomment = 0; /* Indentation of Comment to the right of code
* (-1 -> don't check position) */
prevrhcmt = 0; /* Indentation of previous Comment to the right
* of code (-1 -> don't check position) */
lineno = 0; /* Current line number */
ncomment = 0; /* Comment nesting level on this line */
bnest = 0; /* Brace nesting level on this line */
dnest = 0; /* Data declaration nesting level on this line */
pnest = 0; /* Parenthesis nesting level on this line */
ppifnest = 0; /* #if nesting level on this line */
inasm = 0; /* > 0: Within #ifdef __ASSEMBLY__ */
comment_lineno = -1; /* Line on which the last comment was closed */
blank_lineno = -1; /* Line number of the last blank line */
noblank_lineno = -1; /* A blank line is not needed after this line */
lbrace_lineno = -1; /* Line number of last left brace */
rbrace_lineno = -1; /* Last line containing a right brace */
externc_lineno = -1; /* Last line where 'extern "C"' declared */
/* Process each line in the input stream */
while (fgets(line, LINE_SIZE, instream))
{
lineno++;
indent = 0;
prevbnest = bnest; /* Brace nesting level on the previous line */
prevdnest = dnest; /* Data declaration nesting level on the
* previous line */
prevncomment = ncomment; /* Comment nesting level on the previous line */
bstatm = false; /* True: This line is beginning of a
* statement */
bfor = false; /* REVISIT: Implies for() is all on one line */
/* If we are not in a comment, then this certainly is not a right-hand
* comment.
*/
prevrhcmt = rhcomment;
if (ncomment <= 0)
{
rhcomment = 0;
}
/* Check for a blank line */
for (n = 0; line[n] != '\n' && isspace((int)line[n]); n++)
{
}
if (line[n] == '\n')
{
if (n > 0)
{
ERROR("Blank line contains whitespace", lineno, 1);
}
if (lineno == 1)
{
ERROR("File begins with a blank line", 1, 1);
}
else if (lineno == blank_lineno + 1)
{
ERROR("Too many blank lines", lineno, 1);
}
else if (lineno == lbrace_lineno + 1)
{
ERROR("Blank line follows left brace", lineno, 1);
}
blank_lineno = lineno;
continue;
}
else /* This line is non-blank */
{
/* Check for a missing blank line after a comment */
if (lineno == comment_lineno + 1)
{
/* No blank line should be present if the current line contains
* a right brace, a pre-processor line, the start of another
* comment.
*
* REVISIT: Generates a false alarm if the current line is also
* a comment. Generally it is acceptable for one comment to
* follow another with no space separation.
*
* REVISIT: prevrhcmt is tested to case the preceding line
* contained comments to the right of the code. In such cases,
* the comments are normally aligned and do not follow normal
* indentation rules. However, this code will generate a false
* alarm if the comments are aligned to the right BUT the
* preceding line has no comment.
*/
if (line[n] != '}' && line[n] != '#' && prevrhcmt == 0)
{
ERROR("Missing blank line after comment", comment_lineno,
1);
}
}
/* Files must begin with a comment (the file header).
* REVISIT: Logically, this belongs in the STEP 2 operations
* below.
*/
if (lineno == 1 && (line[n] != '/' || line[n + 1] != '*'))
{
ERROR("Missing file header comment block", lineno, 1);
}
if (lineno == 2)
{
if (line[n] == '*' && line[n + 1] == '\n')
{
ERROR("Missing relative file path in file header", lineno,
n);
}
else if (isspace(line[n + 2]))
{
ERROR("Too many whitespaces before relative file path",
lineno, n);
}
else
{
const char *apps_dir = "apps/";
const size_t apps_len = strlen(apps_dir);
size_t offset;
#ifdef TOPDIR
/* TOPDIR macro contains the absolute path to the "nuttx"
* root directory. It should have been defined via Makefile
* and it is required to accurately evaluate the relative
* path contained in the file header. Otherwise, skip this
* verification.
*/
char *basedir = strstr(g_file_name, TOPDIR);
if (basedir != NULL)
{
/* Add 1 to the offset for the slash character */
offset = strlen(TOPDIR) + 1;
/* Duplicate the line from the beginning of the
* relative file path, removing the '\n' at the end of
* the string.
*/
char *line_dup = strndup(&line[n + 2],
strlen(&line[n + 2]) - 1);
if (strcmp(line_dup, basedir + offset) != 0)
{
ERROR("Relative file path does not match actual file",
lineno, n);
}
free(line_dup);
}
else if (strncmp(&line[n + 2], apps_dir, apps_len) != 0)
{
/* g_file_name neither belongs to "nuttx" repository
* nor begins with the root dir of the other
* repository (e.g. "apps/")
*/
ERROR("Path relative to repository other than \"nuttx\" "
"must begin with the root directory", lineno, n);
}
else
{
#endif
offset = 0;
if (strncmp(&line[n + 2], apps_dir, apps_len) == 0)
{
/* Input file belongs to the "apps" repository */
/* Calculate the offset to the first directory
* after the "apps/" folder.
*/
offset += apps_len;
}
/* Duplicate the line from the beginning of the
* relative file path, removing the '\n' at the end of
* the string.
*/
char *line_dup = strndup(&line[n + 2],
strlen(&line[n + 2]) - 1);
ssize_t base =
strlen(g_file_name) - strlen(&line_dup[offset]);
if (base < 0 ||
(base != 0 && g_file_name[base - 1] != '/') ||
strcmp(&g_file_name[base], &line_dup[offset]) != 0)
{
ERROR("Relative file path does not match actual file",
lineno, n);
}
free(line_dup);
#ifdef TOPDIR
}
#endif
}
}
/* Check for a blank line following a right brace */
if (bfunctions && lineno == rbrace_lineno + 1)
{
/* Check if this line contains a right brace. A right brace
* must be followed by 'else', 'while', 'break', a blank line,
* another right brace, or a pre-processor directive like #endif
*/
if (dnest == 0 &&
strchr(line, '}') == NULL && line[n] != '#' &&
strncmp(&line[n], "else", 4) != 0 &&
strncmp(&line[n], "while", 5) != 0 &&
strncmp(&line[n], "break", 5) != 0)
{
ERROR("Right brace must be followed by a blank line",
rbrace_lineno, n + 1);
}
/* If the right brace is followed by a pre-processor command
* like #endif (but not #else or #elif), then set the right
* brace line number to the line number of the pre-processor
* command (it then must be followed by a blank line)
*/
if (line[n] == '#')
{
int ii;
for (ii = n + 1; line[ii] != '\0' && isspace(line[ii]); ii++)
{
}
if (strncmp(&line[ii], "else", 4) != 0 &&
strncmp(&line[ii], "elif", 4) != 0)
{
rbrace_lineno = lineno;
}
}
}
}
/* STEP 1: Find the indentation level and the start of real stuff on
* the line.
*/
for (n = 0; line[n] != '\n' && isspace((int)line[n]); n++)
{
switch (line[n])
{
case ' ':
{
indent++;
}
break;
case '\t':
{
if (!btabs)
{
ERROR("TABs found. First detected", lineno, n);
btabs = true;
}
indent = (indent + 4) & ~3;
}
break;
case '\r':
{
if (!bcrs)
{
ERROR("Carriage returns found. "
"First detected", lineno, n);
bcrs = true;
}
}
break;
default:
{
snprintf(buffer, sizeof(buffer),
"Unexpected white space character %02x found",
line[n]);
ERROR(buffer, lineno, n);
}
break;
}
}
/* STEP 2: Detect some certain start of line conditions */
/* Skip over pre-processor lines (or continuations of pre-processor
* lines as indicated by ppline)
*/
if (line[indent] == '#' || ppline != PPLINE_NONE)
{
int len;
int ii;
/* Suppress error for comment following conditional compilation */
noblank_lineno = lineno;
/* Check pre-processor commands if this is not a continuation
* line.
*/
ii = indent + 1;
if (ppline == PPLINE_NONE)
{
/* Skip to the pre-processor command following the '#' */
while (line[ii] != '\0' && isspace(line[ii]))
{
ii++;
}
if (line[ii] != '\0')
{
/* Make sure that pre-processor definitions are all in
* the pre-processor definitions section.
*/
ppline = PPLINE_OTHER;
if (strncmp(&line[ii], "define", 6) == 0)
{
ppline = PPLINE_DEFINE;
if (g_section != PRE_PROCESSOR_DEFINITIONS)
{
/* A complication is the header files always have
* the idempotence guard definitions before the
* "Pre-processor Definitions section".
*/
if (g_section == NO_SECTION &&
g_file_type != C_HEADER)
{
/* Only a warning because there is some usage
* of define outside the Pre-processor
* Definitions section which is justifiable.
* Should be manually checked.
*/
WARN("#define outside of 'Pre-processor "
"Definitions' section",
lineno, ii);
}
}
}
/* Make sure that files are included only in the Included
* Files section.
*/
else if (strncmp(&line[ii], "include", 7) == 0)
{
if (g_section != INCLUDED_FILES)
{
/* Only a warning because there is some usage of
* include outside the Included Files section
* which may be is justifiable. Should be
* manually checked.
*/
WARN("#include outside of 'Included Files' "
"section",
lineno, ii);
}
}
else if (strncmp(&line[ii], "if", 2) == 0)
{
ppifnest++;
ppline = PPLINE_IF;
ii += 2;
}
else if (strncmp(&line[ii], "elif", 4) == 0)
{
if (ppifnest == inasm)
{
inasm = 0;
}
ppline = PPLINE_ELIF;
ii += 4;
}
else if (strncmp(&line[ii], "else", 4) == 0)
{
if (ppifnest == inasm)
{
inasm = 0;
}
ppline = PPLINE_ELSE;
}
else if (strncmp(&line[ii], "endif", 4) == 0)
{
if (ppifnest == inasm)
{
inasm = 0;
}
ppifnest--;
ppline = PPLINE_ENDIF;
}
}
}
if (ppline == PPLINE_IF || ppline == PPLINE_ELIF)
{
int bdef = 0;
if (strncmp(&line[ii], "def", 3) == 0)
{
bdef = 1;
ii += 3;
}
else
{
while (line[ii] != '\0' && isspace(line[ii]))
{
ii++;
}
if (strncmp(&line[ii], "defined", 7) == 0)
{
bdef = 1;
ii += 7;
}
}
if (bdef)
{
while (line[ii] != '\0' &&
(isspace(line[ii]) || line[ii] == '('))
{
ii++;
}
if (strncmp(&line[ii], "__ASSEMBLY__", 12) == 0)
{
inasm = ppifnest;
}
}
}
/* Check if the next line will be a continuation of the pre-
* processor command.
*/
len = strlen(&line[indent]) + indent - 1;
if (line[len] == '\n')
{
len--;
}
/* Propagate rhcomment over preprocessor lines Issue #120 */
if (prevrhcmt != 0)
{
/* Don't check position */
rhcomment = -1;
}
lptr = strstr(line, "/*");
if (lptr != NULL)
{
n = lptr - &line[0];
if (line[n + 2] == '\n')
{
ERROR("C comment opening on separate line", lineno, n);
}
else if (!isspace((int)line[n + 2]) && line[n + 2] != '*')
{
ERROR("Missing space after opening C comment", lineno, n);
}
if (strstr(lptr, "*/") == NULL)
{
/* Increment the count of nested comments */
ncomment++;
}
if (ppline == PPLINE_DEFINE)
{
rhcomment = n;
if (prevrhcmt > 0 && n != prevrhcmt)
{
rhcomment = prevrhcmt;
WARN("Wrong column position of comment right of code",
lineno, n);
}
}
else
{
/* Signal rhcomment, but ignore position */
rhcomment = -1;
if (ncomment > 0 &&
(ppline == PPLINE_IF ||
ppline == PPLINE_ELSE ||
ppline == PPLINE_ELIF))
{
/* in #if... and #el... */
ERROR("No multiline comment right of code allowed here",
lineno, n);
}
}
}
if (line[len] != '\\' || ncomment > 0)
{
ppline = PPLINE_NONE;
}
continue;
}
/* Check for a single line comment */
linelen = strlen(line);
if (linelen >= 5) /* Minimum is slash, star, star, slash, newline */
{
lptr = strstr(line, "*/");
if (line[indent] == '/' && line[indent + 1] == '*' &&
lptr - line == linelen - 3)
{
/* If preceding comments were to the right of code, then we can
* assume that there is a columnar alignment of columns that do
* no follow the usual alignment. So the rhcomment flag
* should propagate.
*/
rhcomment = prevrhcmt;
/* Check if there should be a blank line before the comment */
if (lineno > 1 &&
comment_lineno != lineno - 1 &&
blank_lineno != lineno - 1 &&
noblank_lineno != lineno - 1 &&
rhcomment == 0)
{
/* TODO: This generates a false alarm if preceded
* by a label.
*/
ERROR("Missing blank line before comment found", lineno, 1);
}
/* 'comment_lineno 'holds the line number of the last closing
* comment. It is used only to verify that the comment is
* followed by a blank line.
*/
comment_lineno = lineno;
}
}
/* Check for the comment block indicating the beginning of a new file
* section.
*/
if (check_section_header(line, lineno))
{
if (g_section == PRIVATE_FUNCTIONS || g_section == PUBLIC_FUNCTIONS)
{
bfunctions = true; /* Latched */
}
}
/* Check for some kind of declaration.
* REVISIT: The following logic fails for any non-standard types.
* REVISIT: Terminator after keyword might not be a space. Might be
* a newline, for example. struct and unions are often unnamed, for
* example.
*/
else if (inasm == 0)
{
if (strncmp(&line[indent], "auto ", 5) == 0 ||
strncmp(&line[indent], "bool ", 5) == 0 ||
strncmp(&line[indent], "char ", 5) == 0 ||
strncmp(&line[indent], "CODE ", 5) == 0 ||
strncmp(&line[indent], "const ", 6) == 0 ||
strncmp(&line[indent], "double ", 7) == 0 ||
strncmp(&line[indent], "struct ", 7) == 0 ||
strncmp(&line[indent], "struct\n", 7) == 0 || /* May be unnamed */
strncmp(&line[indent], "enum ", 5) == 0 ||
strncmp(&line[indent], "extern ", 7) == 0 ||
strncmp(&line[indent], "EXTERN ", 7) == 0 ||
strncmp(&line[indent], "FAR ", 4) == 0 ||
strncmp(&line[indent], "float ", 6) == 0 ||
strncmp(&line[indent], "int ", 4) == 0 ||
strncmp(&line[indent], "int16_t ", 8) == 0 ||
strncmp(&line[indent], "int32_t ", 8) == 0 ||
strncmp(&line[indent], "long ", 5) == 0 ||
strncmp(&line[indent], "off_t ", 6) == 0 ||
strncmp(&line[indent], "register ", 9) == 0 ||
strncmp(&line[indent], "short ", 6) == 0 ||
strncmp(&line[indent], "signed ", 7) == 0 ||
strncmp(&line[indent], "size_t ", 7) == 0 ||
strncmp(&line[indent], "ssize_t ", 8) == 0 ||
strncmp(&line[indent], "static ", 7) == 0 ||
strncmp(&line[indent], "time_t ", 7) == 0 ||
strncmp(&line[indent], "typedef ", 8) == 0 ||
strncmp(&line[indent], "uint8_t ", 8) == 0 ||
strncmp(&line[indent], "uint16_t ", 9) == 0 ||
strncmp(&line[indent], "uint32_t ", 9) == 0 ||
strncmp(&line[indent], "union ", 6) == 0 ||
strncmp(&line[indent], "union\n", 6) == 0 || /* May be unnamed */
strncmp(&line[indent], "unsigned ", 9) == 0 ||
strncmp(&line[indent], "void ", 5) == 0 ||
strncmp(&line[indent], "volatile ", 9) == 0)
{
/* Check if this is extern "C"; We don't typically indent
* following this.
*/
if (strncmp(&line[indent], "extern \"C\"", 10) == 0)
{
externc_lineno = lineno;
}
/* bfunctions: True: Processing private or public functions.
* bnest: Brace nesting level on this line
* dnest: Data declaration nesting level on this line
*/
/* REVISIT: Also picks up function return types */
/* REVISIT: Logic problem for nested data/function declarations */
if ((!bfunctions || bnest > 0) && dnest == 0)
{
dnest = 1;
}
/* Check for multiple definitions of variables on the line.
* Ignores declarations within parentheses which are probably
* formal parameters.
*/
if (pnest == 0)
{
int tmppnest;
/* Note, we have not yet parsed each character on the line so
* a comma have have been be preceded by '(' on the same line.
* We will have parse up to any comma to see if that is the
* case.
*/
for (i = indent, tmppnest = 0;
line[i] != '\n' && line[i] != '\0';
i++)
{
if (tmppnest == 0 && line[i] == ',')
{
ERROR("Multiple data definitions", lineno, i + 1);
break;
}
else if (line[i] == '(')
{
tmppnest++;
}
else if (line[i] == ')')
{
if (tmppnest < 1)
{
/* We should catch this later */
break;
}
tmppnest--;
}
else if (line[i] == ';')
{
/* Break out if the semicolon terminates the
* declaration is found. Avoids processing any
* righthand comments in most cases.
*/
break;
}
}
}
}
/* Check for a keyword indicating the beginning of a statement.
* REVISIT: This, obviously, will not detect statements that do not
* begin with a C keyword (such as assignment statements).
*/
else if (strncmp(&line[indent], "break ", 6) == 0 ||
strncmp(&line[indent], "case ", 5) == 0 ||
#if 0 /* Part of switch */
strncmp(&line[indent], "case ", 5) == 0 ||
#endif
strncmp(&line[indent], "continue ", 9) == 0 ||
#if 0 /* Part of switch */
strncmp(&line[indent], "default ", 8) == 0 ||
#endif
strncmp(&line[indent], "do ", 3) == 0 ||
strncmp(&line[indent], "else ", 5) == 0 ||
strncmp(&line[indent], "goto ", 5) == 0 ||
strncmp(&line[indent], "if ", 3) == 0 ||
strncmp(&line[indent], "return ", 7) == 0 ||
#if 0 /* Doesn't follow pattern */
strncmp(&line[indent], "switch ", 7) == 0 ||
#endif
strncmp(&line[indent], "while ", 6) == 0)
{
bstatm = true;
}
/* Spacing works a little differently for and switch statements */
else if (strncmp(&line[indent], "for ", 4) == 0)
{
bfor = true;
bstatm = true;
}
else if (strncmp(&line[indent], "switch ", 7) == 0)
{
bswitch = true;
}
/* Also check for C keywords with missing white space */
else if (strncmp(&line[indent], "do(", 3) == 0 ||
strncmp(&line[indent], "if(", 3) == 0 ||
strncmp(&line[indent], "while(", 6) == 0)
{
ERROR("Missing whitespace after keyword", lineno, n);
bstatm = true;
}
else if (strncmp(&line[indent], "for(", 4) == 0)
{
ERROR("Missing whitespace after keyword", lineno, n);
bfor = true;
bstatm = true;
}
else if (strncmp(&line[indent], "switch(", 7) == 0)
{
ERROR("Missing whitespace after keyword", lineno, n);
bswitch = true;
}
}
/* STEP 3: Parse each character on the line */
bquote = false; /* True: Backslash quoted character next */
bblank = true; /* Used to verify block comment terminator */
for (; line[n] != '\n' && line[n] != '\0'; n++)
{
/* Report any use of non-standard white space characters */
if (isspace(line[n]))
{
if (line[n] == '\t')
{
if (!btabs)
{
ERROR("TABs found. First detected", lineno, n);
btabs = true;
}
}
else if (line[n] == '\r')
{
if (!bcrs)
{
ERROR("Carriage returns found. "
"First detected", lineno, n);
bcrs = true;
}
}
else if (line[n] != ' ')
{
snprintf(buffer, sizeof(buffer),
"Unexpected white space character %02x found",
line[n]);
ERROR(buffer, lineno, n);
}
}
/* Skip over identifiers */
if (ncomment == 0 && !bstring && (line[n] == '_' || isalpha(line[n])))
{
bool have_upper = false;
bool have_lower = false;
int ident_index = n;
/* Parse over the identifier. Check if it contains mixed upper-
* and lower-case characters.
*/
do
{
have_upper |= isupper(line[n]);
/* The coding standard provides for some exceptions of lower
* case characters in pre-processor strings:
*
* IPv[4|6] as an IP version number
* ICMPv6 as an ICMP version number
* IGMPv2 as an IGMP version number
* [0-9]p[0-9] as a decimal point
* d[0-9] as a divisor
* Hz for frequencies (including KHz, MHz, etc.)
*/
if (!have_lower && islower(line[n]))
{
switch (line[n])
{
/* A sequence containing 'v' may occur at the
* beginning of the identifier.
*/
case 'v':
if (n > 1 &&
line[n - 2] == 'I' &&
line[n - 1] == 'P' &&
(line[n + 1] == '4' ||
line[n + 1] == '6'))
{
}
else if (n > 3 &&
line[n - 4] == 'I' &&
line[n - 3] == 'C' &&
line[n - 2] == 'M' &&
line[n - 1] == 'P' &&
line[n + 1] == '6')
{
}
else if (n > 3 &&
line[n - 4] == 'I' &&
line[n - 3] == 'G' &&
line[n - 2] == 'M' &&
line[n - 1] == 'P' &&
line[n + 1] == '2')
{
}
else
{
have_lower = true;
}
break;
/* Sequences containing 'p', 'd', or 'z' must have
* been preceded by upper case characters.
*/
case 'p':
if (!have_upper || n < 1 ||
!isdigit(line[n - 1]) ||
!isdigit(line[n + 1]))
{
have_lower = true;
}
break;
case 'd':
if (!have_upper || !isdigit(line[n + 1]))
{
have_lower = true;
}
break;
case 'z':
if (!have_upper || n < 1 ||
line[n - 1] != 'H')
{
have_lower = true;
}
break;
break;
default:
have_lower = true;
break;
}
}
n++;
}
while (line[n] == '_' || isalnum(line[n]));
/* Check for mixed upper and lower case */
if (have_upper && have_lower)
{
/* Ignore symbols that begin with white-listed prefixes */
if (white_list(&line[ident_index], lineno))
{
/* No error */
}
/* Special case hex constants. These will look like
* identifiers starting with 'x' or 'X' but preceded
* with '0'
*/
else if (ident_index < 1 ||
(line[ident_index] != 'x' &&
line[ident_index] != 'X') ||
line[ident_index - 1] != '0')
{
ERROR("Mixed case identifier found",
lineno, ident_index);
}
else if (have_upper)
{
ERROR("Upper case hex constant found",
lineno, ident_index);
}
}
/* Check if the identifier is the last thing on the line */
if (line[n] == '\n' || line[n] == '\0')
{
break;
}
}
/* Handle comments */
if (line[n] == '/' && !bstring)
{
/* Check for start of a C comment */
if (line[n + 1] == '*')
{
if (line[n + 2] == '\n')
{
ERROR("C comment opening on separate line", lineno, n);
}
else if (!isspace((int)line[n + 2]) && line[n + 2] != '*')
{
ERROR("Missing space after opening C comment", lineno, n);
}
/* Increment the count of nested comments */
ncomment++;
/* If there is anything to the left of the left brace, then
* this must be a comment to the right of code.
* Also if preceding comments were to the right of code, then
* we can assume that there is a columnar alignment of columns
* that do no follow the usual alignment. So the rhcomment
* flag should propagate.
*/
if (prevrhcmt == 0)
{
if (n != indent)
{
rhcomment = n;
}
}
else
{
rhcomment = n;
if (prevrhcmt > 0 && n != prevrhcmt)
{
rhcomment = prevrhcmt;
if (n != indent)
{
WARN("Wrong column position of "
"comment right of code", lineno, n);
}
else
{
ERROR("Wrong column position or missing "
"blank line before comment", lineno, n);
}
}
}
n++;
continue;
}
/* Check for end of a C comment */
else if (n > 0 && line[n - 1] == '*')
{
if (n < 2)
{
ERROR("Closing C comment not indented", lineno, n);
}
else if (!isspace((int)line[n - 2]) && line[n - 2] != '*')
{
ERROR("Missing space before closing C comment", lineno,
n);
}
/* Check for block comments that are not on a separate line.
* This would be the case if we are we are within a comment
* that did not start on this line and the current line is
* not blank up to the point where the comment was closed.
*/
if (prevncomment > 0 && !bblank && rhcomment == 0)
{
ERROR("Block comment terminator must be on a "
"separate line", lineno, n);
}
#if 0
/* REVISIT: Generates false alarms when portions of an
* expression are commented out within the expression.
*/
if (line[n + 1] != '\n')
{
ERROR("Garbage on line after C comment", lineno, n);
}
#endif
/* Handle nested comments */
if (ncomment > 0)
{
/* Remember the line number of the line containing the
* closing of the outermost comment.
*/
if (--ncomment == 0)
{
/* 'comment_lineno 'holds the line number of the
* last closing comment. It is used only to
* verify that the comment is followed by a blank
* line.
*/
comment_lineno = lineno;
/* Note that rhcomment must persist to support a
* later test for comment alignment. We will fix
* that at the top of the loop when ncomment == 0.
*/
}
}
else
{
/* Note that rhcomment must persist to support a later
* test for comment alignment. We will will fix that
* at the top of the loop when ncomment == 0.
*/
ncomment = 0;
ERROR("Closing without opening comment", lineno, n);
}
n++;
continue;
}
/* Check for C++ style comments */
else if (line[n + 1] == '/')
{
/* Check for URI schemes, e.g. "http://" or "https://" */
if ((ncomment == 0) &&
(n == 0 || strncmp(&line[n - 1], "://", 3) != 0))
{
ERROR("C++ style comment", lineno, n);
n++;
continue;
}
}
}
/* Check if the line is blank so far. This is only used to
* to verify the the closing of a block comment is on a separate
* line. So we also need to treat '*' as a 'blank'.
*/
if (!isblank(line[n]) && line[n] != '*')
{
bblank = false;
}
/* Check for a string... ignore if we are in the middle of a
* comment.
*/
if (ncomment == 0)
{
/* Backslash quoted character */
if (line[n] == '\\')
{
bquote = true;
n++;
}
/* Check for quoted characters: \" in string */
if (line[n] == '"' && !bquote)
{
bstring = !bstring;
}
bquote = false;
}
/* The rest of the line is only examined of we are not in a comment,
* in a string or in assembly.
*
* REVISIT: Should still check for whitespace at the end of the
* line.
*/
if (ncomment == 0 && !bstring && inasm == 0)
{
switch (line[n])
{
/* Handle logic nested with curly braces */
case '{':
{
if (n > indent)
{
/* REVISIT: dnest is always > 0 here if bfunctions ==
* false.
*/
if (dnest == 0 || !bfunctions || lineno == rbrace_lineno)
{
ERROR("Left bracket not on separate line", lineno,
n);
}
}
else if (line[n + 1] != '\n')
{
if (dnest == 0)
{
ERROR("Garbage follows left bracket", lineno, n);
}
}
bnest++;
if (dnest > 0)
{
dnest++;
}
/* Check if we are within 'extern "C"', we don't
* normally indent in that case because the 'extern "C"'
* is conditioned on __cplusplus.
*/
if (lineno == externc_lineno ||
lineno - 1 == externc_lineno)
{
bexternc = true;
}
/* Suppress error for comment following a left brace */
noblank_lineno = lineno;
lbrace_lineno = lineno;
}
break;
case '}':
{
/* Decrement the brace nesting level */
if (bnest < 1)
{
ERROR("Unmatched right brace", lineno, n);
}
else
{
bnest--;
if (bnest < 1)
{
bnest = 0;
bswitch = false;
}
}
/* Decrement the declaration nesting level */
if (dnest < 3)
{
dnest = 0;
bexternc = false;
}
else
{
dnest--;
}
/* The right brace should be on a separate line */
if (n > indent)
{
if (dnest == 0)
{
ERROR("Right bracket not on separate line",
lineno, n);
}
}
/* Check for garbage following the left brace */
if (line[n + 1] != '\n' &&
line[n + 1] != ',' &&
line[n + 1] != ';')
{
int sndx = n + 1;
bool whitespace = false;
/* Skip over spaces */
while (line[sndx] == ' ')
{
sndx++;
}
/* One possibility is that the right bracket is
* followed by an identifier then a semi-colon.
* Comma is possible to but would be a case of
* multiple declaration of multiple instances.
*/
if (line[sndx] == '_' || isalpha(line[sndx]))
{
int endx = sndx;
/* Skip to the end of the identifier. Checking
* for mixed case identifiers will be done
* elsewhere.
*/
while (line[endx] == '_' ||
isalnum(line[endx]))
{
endx++;
}
/* Skip over spaces */
while (line[endx] == ' ')
{
whitespace = true;
endx++;
}
/* Handle according to what comes after the
* identifier.
*/
if (strncmp(&line[sndx], "while", 5) == 0)
{
ERROR("'while' must be on a separate line",
lineno, sndx);
}
else if (line[endx] == ',')
{
ERROR("Multiple data definitions on line",
lineno, endx);
}
else if (line[endx] == ';')
{
if (whitespace)
{
ERROR("Space precedes semi-colon",
lineno, endx);
}
}
else if (line[endx] == '=')
{
/* There's a struct initialization following */
check_spaces_leftright(line, lineno, endx, endx);
dnest = 1;
}
else
{
ERROR("Garbage follows right bracket",
lineno, n);
}
}
else
{
ERROR("Garbage follows right bracket", lineno, n);
}
}
/* The right brace should not be preceded with a a blank
* line.
*/
if (lineno == blank_lineno + 1)
{
ERROR("Blank line precedes right brace at line",
lineno, 1);
}
rbrace_lineno = lineno;
}
break;
/* Handle logic with parentheses */
case '(':
{
/* Increase the parenthetical nesting level */
pnest++;
/* Check for inappropriate space around parentheses */
if (line[n + 1] == ' ') /* && !bfor */
{
ERROR("Space follows left parenthesis", lineno, n);
}
}
break;
case ')':
{
/* Decrease the parenthetical nesting level */
if (pnest < 1)
{
ERROR("Unmatched right parentheses", lineno, n);
pnest = 0;
}
else
{
pnest--;
}
/* Allow ')' as first thing on the line (n == indent)
* Allow "for (xx; xx; )" (bfor == true)
*/
if (n > 0 && n != indent && line[n - 1] == ' ' && !bfor)
{
ERROR("Space precedes right parenthesis", lineno, n);
}
}
break;
/* Check for inappropriate space around square brackets */
case '[':
{
if (line[n + 1] == ' ')
{
ERROR("Space follows left bracket", lineno, n);
}
}
break;
case ']':
{
if (n > 0 && line[n - 1] == ' ')
{
ERROR("Space precedes right bracket", lineno, n);
}
}
break;
/* Semi-colon may terminate a declaration */
case ';':
{
if (!isspace((int)line[n + 1]))
{
ERROR("Missing whitespace after semicolon", lineno, n);
}
/* Semicolon terminates a declaration/definition if there
* was no left curly brace (i.e., dnest is only 1).
*/
if (dnest == 1)
{
dnest = 0;
}
}
break;
/* Semi-colon may terminate a declaration */
case ',':
{
if (!isspace((int)line[n + 1]))
{
ERROR("Missing whitespace after comma", lineno, n);
}
}
break;
/* Skip over character constants */
case '\'':
{
int endndx = n + 2;
if (line[n + 1] != '\n' && line[n + 1] != '\0')
{
if (line[n + 1] == '\\')
{
for (;
line[endndx] != '\n' &&
line[endndx] != '\0' &&
line[endndx] != '\'';
endndx++);
}
n = endndx;
}
}
break;
/* Check for space around various operators */
case '-':
/* -> */
if (line[n + 1] == '>')
{
/* -> must have no whitespaces on its left or right */
check_nospaces_leftright(line, lineno, n, n + 1);
n++;
}
/* -- */
else if (line[n + 1] == '-')
{
/* "--" should be next to its operand. If there are
* whitespaces or non-operand characters on both left
* and right (e.g. "a -- " “a[i --]”, "(-- i)"),
* there's an error.
*/
check_operand_leftright(line, lineno, n, n + 1);
n++;
}
/* -= */
else if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
/* Scientific notation with a negative exponent (eg. 10e-10)
* REVISIT: This fails for cases where the variable name
* ends with 'e' preceded by a digit:
* a = abc1e-10;
* a = ABC1E-10;
*/
else if ((line[n - 1] == 'e' || line[n - 1] == 'E') &&
isdigit(line[n + 1]) && isdigit(line[n - 2]))
{
n++;
}
else
{
/* '-' may function as a unary operator and snuggle
* on the left.
*/
check_spaces_left(line, lineno, n);
}
break;
case '+':
/* ++ */
if (line[n + 1] == '+')
{
/* "++" should be next to its operand. If there are
* whitespaces or non-operand characters on both left
* and right (e.g. "a ++ " “a[i ++]”, "(++ i)"),
* there's an error.
*/
check_operand_leftright(line, lineno, n, n + 1);
n++;
}
/* += */
else if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
/* '+' may function as a unary operator and snuggle
* on the left.
*/
check_spaces_left(line, lineno, n);
}
break;
case '&':
/* &<variable> OR &(<expression>) */
if (isalpha((int)line[n + 1]) || line[n + 1] == '_' ||
line[n + 1] == '(')
{
}
/* &&, &= */
else if (line[n + 1] == '=' || line[n + 1] == '&')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '/':
/* C comment terminator */
if (line[n - 1] == '*')
{
n++;
}
/* C++-style comment */
else if (line[n + 1] == '/')
{
/* Check for "http://" or "https://" */
if ((n < 5 || strncmp(&line[n - 5], "http://", 7) != 0) &&
(n < 6 || strncmp(&line[n - 6], "https://", 8) != 0))
{
ERROR("C++ style comment on at %d:%d\n",
lineno, n);
}
n++;
}
/* /= */
else if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
/* Division operator */
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '*':
/* *\/, ** */
if (line[n] == '*' &&
(line[n + 1] == '/' ||
line[n + 1] == '*'))
{
n++;
break;
}
/* *<variable>, *(<expression>) */
else if (isalpha((int)line[n + 1]) ||
line[n + 1] == '_' ||
line[n + 1] == '(')
{
break;
}
/* (<type> *) */
else if (line[n + 1] == ')')
{
/* REVISIT: This gives false alarms on syntax like *--ptr */
if (line[n - 1] != ' ' && line[n - 1] != '(')
{
ERROR("Operator/assignment must be preceded "
"with whitespace", lineno, n);
}
break;
}
/* *= */
else if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
/* A single '*' may be an binary operator, but
* it could also be a unary operator when used to deference
* a pointer.
*/
check_spaces_left(line, lineno, n);
}
break;
case '%':
/* %= */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '<':
/* <=, <<, <<= */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else if (line[n + 1] == '<')
{
if (line[n + 2] == '=')
{
check_spaces_leftright(line, lineno, n, n + 2);
n += 2;
}
else
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '>':
/* >=, >>, >>= */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else if (line[n + 1] == '>')
{
if (line[n + 2] == '=')
{
check_spaces_leftright(line, lineno, n, n + 2);
n += 2;
}
else
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '|':
/* |=, || */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else if (line[n + 1] == '|')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '^':
/* ^= */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '=':
/* == */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
else
{
check_spaces_leftright(line, lineno, n, n);
}
break;
case '~':
check_spaces_left(line, lineno, n);
break;
case '!':
/* != */
if (line[n + 1] == '=')
{
check_spaces_leftright(line, lineno, n, n + 1);
n++;
}
/* !! */
else if (line[n + 1] == '!')
{
check_spaces_left(line, lineno, n);
n++;
}
else
{
check_spaces_left(line, lineno, n);
}
break;
default:
break;
}
}
}
/* Loop terminates when NUL or newline character found */
if (line[n] == '\n' || line[n] == '\0')
{
/* If the parse terminated on the NULL, then back up to the last
* character (which should be the newline).
*/
int m = n;
if (line[m] == '\0' && m > 0)
{
m--;
}
/* Check for space at the end of the line. Except for carriage
* returns which we have already reported (one time) above.
*/
if (m > 1 && isspace((int)line[m - 1]) &&
line[m - 1] != '\n' && line[m - 1] != '\r')
{
ERROR("Dangling whitespace at the end of line", lineno, m);
}
/* The line width is determined by the location of the final
* asterisk in block comments. The closing line of the block
* comment will exceed that by one one character, the '/'
* following the final asterisk.
*/
else if (m > g_maxline)
{
bool bslash;
int a;
for (bslash = false, a = m;
a > 2 && strchr("\n\r/", line[a]) != NULL;
a--)
{
if (line[a] == '/')
{
bslash = true;
}
}
if (bslash && line[a] == '*')
{
m = a + 1;
}
}
/* Check for long lines
*
* REVISIT: Long line checks suppressed on right hand comments
* for now. This just prevents a large number of difficult-to-
* fix complaints that we would have otherwise.
*/
if (m > g_maxline && !rhcomment)
{
ERROR("Long line found", lineno, m);
}
}
/* STEP 4: Check alignment */
/* Within a comment block, we need only check on the alignment of the
* comment.
*/
if ((ncomment > 0 || prevncomment > 0) && !bstring)
{
/* Nothing should begin in comment zero */
if (indent == 0 && line[0] != '/' && !bexternc)
{
/* NOTE: if this line contains a comment to the right of the
* code, then ncomment will be misleading because it was
* already incremented above.
*/
if (ncomment > 1 || rhcomment == 0)
{
ERROR("No indentation line", lineno, indent);
}
}
else if (indent == 1 && line[0] == ' ' && line[1] == '*')
{
/* Good indentation */
}
else if (indent > 0 && line[indent] == '\n')
{
ERROR("Whitespace on blank line", lineno, indent);
}
else if (indent > 0 && indent < 2)
{
if (bnest > 0)
{
ERROR("Insufficient indentation", lineno, indent);
}
else
{
ERROR("Expected indentation line", lineno, indent);
}
}
else if (indent > 0 && !bswitch)
{
if (line[indent] == '/')
{
/* Comments should like at offsets 2, 6, 10, ...
* This rule is not followed, however, if the comments are
* aligned to the right of the code.
*/
if ((indent & 3) != 2 && rhcomment == 0)
{
ERROR("Bad comment alignment", lineno, indent);
}
/* REVISIT: This screws up in cases where there is C code,
* followed by a comment that continues on the next line.
*/
else if (line[indent + 1] != '*')
{
ERROR("Missing asterisk in comment", lineno, indent);
}
}
else if (line[indent] == '*')
{
/* REVISIT: Generates false alarms on comments at the end of
* the line if there is nothing preceding (such as the aligned
* comments with a structure field definition). So disabled
* for comments before beginning of function definitions.
*
* Suppress this error if this is a comment to the right of
* code.
* Those may be unaligned.
*/
if ((indent & 3) != 3 && bfunctions && dnest == 0 &&
rhcomment == 0)
{
ERROR("Bad comment block alignment", lineno, indent);
}
if (line[indent + 1] != ' ' &&
line[indent + 1] != '*' &&
line[indent + 1] != '\n' &&
line[indent + 1] != '/')
{
ERROR("Invalid character after asterisk "
"in comment block", lineno, indent);
}
}
/* If this is not the line containing the comment start, then this
* line should begin with '*'
*/
else if (prevncomment > 0)
{
ERROR("Missing asterisk in comment block", lineno, indent);
}
}
}
/* Check for various alignment outside of the comment block */
else if ((ncomment == 0 && prevncomment == 0) && !bstring)
{
if (indent == 0 && strchr("\n#{}", line[0]) == NULL)
{
/* Ignore if we are at global scope */
if (prevbnest > 0)
{
bool blabel = false;
if (isalpha((int)line[indent]))
{
for (i = indent + 1; isalnum((int)line[i]) ||
line[i] == '_'; i++);
blabel = (line[i] == ':');
}
if (!blabel && !bexternc)
{
ERROR("No indentation line", lineno, indent);
}
}
}
else if (indent == 1 && line[0] == ' ' && line[1] == '*')
{
/* Good indentation */
}
else if (indent > 0 && line[indent] == '\n')
{
ERROR("Whitespace on blank line", lineno, indent);
}
else if (indent > 0 && indent < 2)
{
ERROR("Insufficient indentation line", lineno, indent);
}
else if (line[indent] == '{')
{
/* Check for left brace in first column, but preceded by a
* blank line. Should never happen (but could happen with
* internal compound statements).
*/
if (indent == 0 && lineno == blank_lineno + 1)
{
ERROR("Blank line before opening left brace", lineno, indent);
}
/* REVISIT: Possible false alarms in compound statements
* without a preceding conditional. That usage often violates
* the coding standard.
*/
else if (!bfunctions && (indent & 1) != 0)
{
ERROR("Bad left brace alignment", lineno, indent);
}
else if ((indent & 3) != 0 && !bswitch && dnest == 0)
{
ERROR("Bad left brace alignment", lineno, indent);
}
}
else if (line[indent] == '}')
{
/* REVISIT: Possible false alarms in compound statements
* without a preceding conditional. That usage often violates
* the coding standard.
*/
if (!bfunctions && (indent & 1) != 0)
{
ERROR("Bad left brace alignment", lineno, indent);
}
else if ((indent & 3) != 0 && !bswitch && prevdnest == 0)
{
ERROR("Bad right brace alignment", lineno, indent);
}
}
else if (indent > 0)
{
/* REVISIT: Generates false alarms when a statement continues on
* the next line. The bstatm check limits to lines beginning
* with C keywords.
* REVISIT: The bstatm check will not detect statements that
* do not begin with a C keyword (such as assignment statements).
* REVISIT: Generates false alarms on comments at the end of
* the line if there is nothing preceding (such as the aligned
* comments with a structure field definition). So disabled for
* comments before beginning of function definitions.
*/
if ((bstatm || /* Begins with C keyword */
(line[indent] == '/' &&
bfunctions &&
line[indent + 1] == '*')) && /* Comment in functions */
!bswitch && /* Not in a switch */
dnest == 0) /* Not a data definition */
{
if ((indent & 3) != 2)
{
ERROR("Bad alignment", lineno, indent);
}
}
/* Crazy cases. There should be no small odd alignments
* outside of comment/string. Odd alignments are possible
* on continued lines, but not if they are small.
*/
else if (indent == 1 || indent == 3)
{
ERROR("Small odd alignment", lineno, indent);
}
}
}
}
if (!bfunctions && g_file_type == C_SOURCE)
{
ERROR("\"Private/Public Functions\" not found!"
" File will not be checked", lineno, 1);
}
if (ncomment > 0 || bstring)
{
ERROR("Comment or string found at end of file", lineno, 1);
}
fclose(instream);
if (g_verbose == 1)
{
fprintf(stderr, "%s: %s nxstyle check\n", g_file_name,
g_status == 0 ? "PASSED" : "FAILED");
}
return g_status;
}