diff --git a/src/ccompiler/parser.c b/src/ccompiler/parser.c index 17b6eb3..5f10389 100644 --- a/src/ccompiler/parser.c +++ b/src/ccompiler/parser.c @@ -31,6 +31,7 @@ typedef struct { size_t typedef_scope_count; size_t typedef_scope_capacity; size_t expression_depth; + size_t statement_depth; } CCParser; static const char *const cc_ast_kind_names[] = { @@ -271,6 +272,25 @@ static void cc_leave_expression_nesting(CCParser *parser) { parser->expression_depth--; } +/* Statement parsing recurses per nesting level too, through compound blocks + * and the bodies of if/while/for/do/switch. Nested braces alone are enough to + * exhaust the stack, so the same cap applies one level up. */ +#define CC_MAX_STATEMENT_NESTING 200 + +static bool cc_enter_statement_nesting(CCParser *parser) { + if (parser->statement_depth >= CC_MAX_STATEMENT_NESTING) { + cc_expected(parser, "statement nesting too deep"); + return false; + } + + parser->statement_depth++; + return true; +} + +static void cc_leave_statement_nesting(CCParser *parser) { + parser->statement_depth--; +} + static CCAstNode *cc_new_ast_node(CCAstKind kind, CCSpan span, const char *text) { CCAstNode *node; @@ -579,6 +599,7 @@ static void cc_synchronize_to_semicolon_or_brace(CCParser *parser) { static CCAstNode *cc_parse_expression(CCParser *parser); static CCAstNode *cc_parse_assignment_expression(CCParser *parser); static CCAstNode *cc_parse_statement(CCParser *parser); +static CCAstNode *cc_parse_statement_body(CCParser *parser); static CCAstNode *cc_parse_declaration(CCParser *parser); static CCParsedDeclarator cc_parse_declarator(CCParser *parser, bool allow_abstract); @@ -1809,6 +1830,18 @@ static CCAstNode *cc_parse_expression_statement(CCParser *parser) { } static CCAstNode *cc_parse_statement(CCParser *parser) { + CCAstNode *statement; + + if (!cc_enter_statement_nesting(parser)) { + return NULL; + } + + statement = cc_parse_statement_body(parser); + cc_leave_statement_nesting(parser); + return statement; +} + +static CCAstNode *cc_parse_statement_body(CCParser *parser) { if (cc_current_token(parser)->kind == CC_TOKEN_IDENTIFIER && cc_peek_token(parser, 1)->kind == CC_TOKEN_COLON) { return cc_parse_labeled_statement(parser); } diff --git a/src/ccompiler/sema_const.c b/src/ccompiler/sema_const.c index 43f679b..2e7b17a 100644 --- a/src/ccompiler/sema_const.c +++ b/src/ccompiler/sema_const.c @@ -33,7 +33,7 @@ static bool cc_parse_integer_literal(const char *text, long long *value) { copy[length] = '\0'; if (length > 2 && copy[0] == '0' && (copy[1] == 'b' || copy[1] == 'B')) { - long long parsed; + unsigned long long parsed; parsed = 0; for (index = 2; index < length; index++) { @@ -41,9 +41,11 @@ static bool cc_parse_integer_literal(const char *text, long long *value) { free(copy); return false; } - parsed = (parsed << 1) | (copy[index] - '0'); + /* Accumulate unsigned: shifting a signed value up into the sign bit + * is undefined, and a 64-bit literal does exactly that. */ + parsed = (parsed << 1) | (unsigned long long)(copy[index] - '0'); } - *value = parsed; + *value = (long long)parsed; free(copy); return true; } diff --git a/tests/binary_literal_overflow.c b/tests/binary_literal_overflow.c new file mode 100644 index 0000000..7f20ddf --- /dev/null +++ b/tests/binary_literal_overflow.c @@ -0,0 +1,6 @@ +/* Regression test: the 0b fold accumulated into a signed long long, so a + * literal whose top bit lands in the sign position shifted into it. That is + * undefined behaviour, not a wrapped value, and UBSan flagged it. The fold now + * accumulates unsigned and converts once on the way out. + */ +int main(void){ switch (0) { case 0b1111111111111111111111111111111111111111111111111111111111111111: return 1; } return 0; } diff --git a/tests/data/smoke_cases.txt b/tests/data/smoke_cases.txt index 1cd2423..ff3e637 100644 --- a/tests/data/smoke_cases.txt +++ b/tests/data/smoke_cases.txt @@ -11,8 +11,13 @@ parse|tests/pathological_statement.c|1|expected expression parse|tests/pathological_conditional.c|1|expected expression # Regression: deep nesting is capped instead of overflowing the stack parse|tests/pathological_nesting.c|1|expression nesting too deep +# Regression: statement nesting is capped too, whether or not blocks are involved +parse|tests/pathological_statement_nesting.c|1|statement nesting too deep +parse|tests/pathological_dangling_if.c|1|statement nesting too deep # Regression: self-including headers hit the include-depth cap, not the stack preprocess|tests/include_cycle_a.c|1|include nesting too deep # Regression: const folding must reject UB shifts/overflow instead of tripping UBSan check|tests/shift_too_large.c|1|case label must be an integer constant expression check|tests/const_overflow.c|1|case label must be an integer constant expression +# Regression: a 0b literal wide enough to reach the sign bit must fold without UB +check|tests/binary_literal_overflow.c|0|semantic analysis succeeded diff --git a/tests/pathological_dangling_if.c b/tests/pathological_dangling_if.c new file mode 100644 index 0000000..2386019 --- /dev/null +++ b/tests/pathological_dangling_if.c @@ -0,0 +1,8 @@ +/* Regression test: the depth cap covers brace-less nesting too. A chain of + * dangling ifs recurses through the statement parser without ever opening a + * block, so a cap on compound statements alone would not have caught it. + */ +int main(void) { + if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) if (1) return 0; + return 0; +} diff --git a/tests/pathological_statement_nesting.c b/tests/pathological_statement_nesting.c new file mode 100644 index 0000000..8dcb401 --- /dev/null +++ b/tests/pathological_statement_nesting.c @@ -0,0 +1,9 @@ +/* Regression test: statement nesting is capped the way expression nesting + * already is. Nested blocks recurse through the statement and + * compound-statement parsers with no other bound, which overflows the stack + * on deep input. The parser now reports "statement nesting too deep". + */ +int main(void) { +{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} + return 0; +}