From 9743ef24ae6d73ee93b673ed2d2bb8cf079cef12 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:21 +0300 Subject: [PATCH] parser: fix defer not being executed in compiler code (fixes #14438) --- vlib/v/builder/builder.v | 26 +++++++++ vlib/v/parser/messages.v | 61 +++++++++----------- vlib/v/parser/parse_text_defer_stdout_test.v | 16 +++++ vlib/v/parser/parser.v | 8 ++- 4 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 vlib/v/parser/parse_text_defer_stdout_test.v diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 098c93f55..3f5472f96 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -105,7 +105,13 @@ pub fn new_builder(pref_ &pref.Preferences) Builder { pub fn (mut b Builder) interpret_text(code string, v_files []string) ! { b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) b.parsed_files << parser.parse_text(code, '', mut b.table, .skip_comments, b.pref) + if b.should_stop_after_frontend_error() && b.has_frontend_errors() { + exit(1) + } b.parse_imports() + if b.should_stop_after_frontend_error() && b.has_frontend_errors() { + exit(1) + } if b.pref.only_check_syntax { return error_with_code('stop_after_parser', 7001) @@ -125,8 +131,14 @@ pub fn (mut b Builder) front_stages(v_files []string) ! { util.timing_start('Builder.front_stages.parse_files') b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) timers.show('Builder.front_stages.parse_files') + if b.should_stop_after_frontend_error() && b.has_frontend_errors() { + exit(1) + } b.parse_imports() + if b.should_stop_after_frontend_error() && b.has_frontend_errors() { + exit(1) + } timers.show('SCAN') timers.show('PARSE') @@ -202,6 +214,17 @@ pub fn (mut b Builder) front_and_middle_stages(v_files []string) ! { b.middle_stages()! } +@[inline] +fn (b &Builder) should_stop_after_frontend_error() bool { + return b.pref.fatal_errors + || (b.pref.output_mode == .stdout && !b.pref.check_only && !b.pref.is_vls) +} + +@[inline] +fn (b &Builder) has_frontend_errors() bool { + return b.parsed_files.any(it.errors.len > 0) +} + // parse all deps from already parsed files pub fn (mut b Builder) parse_imports() { util.timing_start(@METHOD) @@ -279,6 +302,9 @@ pub fn (mut b Builder) parse_imports() { } } b.parsed_files << parsed_files + if b.should_stop_after_frontend_error() && parsed_files.any(it.errors.len > 0) { + return + } done_imports << mod } } diff --git a/vlib/v/parser/messages.v b/vlib/v/parser/messages.v index 6ea9b8b36..453dfa868 100644 --- a/vlib/v/parser/messages.v +++ b/vlib/v/parser/messages.v @@ -69,34 +69,28 @@ fn (mut p Parser) error_with_pos(s string, pos token.Pos) ast.NodeError { // print_backtrace() mut kind := 'error:' file_path := if pos.file_idx < 0 { p.file_path } else { p.table.filelist[pos.file_idx] } - if p.pref.fatal_errors { - util.show_compiler_message(kind, pos: pos, file_path: file_path, message: s) - exit(1) - } - if p.pref.output_mode == .stdout && !p.pref.check_only && !p.is_vls { + should_abort_after_print := p.pref.fatal_errors + || (p.pref.output_mode == .stdout && !p.pref.check_only && !p.is_vls) + if should_abort_after_print { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' } util.show_compiler_message(kind, pos: pos, file_path: file_path, message: s) - exit(1) - } else { - p.errors << errors.Error{ - file_path: file_path - pos: pos - reporter: .parser - message: s - } - - // To avoid getting stuck after an error, the parser - // will proceed to the next token. - if p.pref.check_only || p.pref.only_check_syntax { - if p.tok.kind != .eof { - p.next() - } - } + p.should_abort = true } - if p.pref.output_mode == .silent && p.tok.kind != .eof { + p.errors << errors.Error{ + file_path: file_path + pos: pos + reporter: .parser + message: s + } + + // To avoid getting stuck after an error, the parser will always + // proceed to the next token in modes where parsing continues, or + // while it is unwinding after a printed error. + if (should_abort_after_print || p.pref.check_only || p.pref.only_check_syntax + || p.pref.output_mode == .silent) && p.tok.kind != .eof { // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. // In the silent mode however, the parser continues to run, even though it would have stopped. Some // of the parser logic does not expect that, and may loop forever. @@ -111,25 +105,22 @@ fn (mut p Parser) error_with_pos(s string, pos token.Pos) ast.NodeError { fn (mut p Parser) error_with_error(error errors.Error) { mut kind := 'error:' - if p.pref.fatal_errors { - util.show_compiler_message(kind, error.CompilerMessage) - exit(1) - } - if p.pref.output_mode == .stdout && !p.pref.check_only { + should_abort_after_print := p.pref.fatal_errors + || (p.pref.output_mode == .stdout && !p.pref.check_only) + if should_abort_after_print { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' } util.show_compiler_message(kind, error.CompilerMessage) - exit(1) - } else { - if p.pref.message_limit >= 0 && p.errors.len >= p.pref.message_limit { - p.should_abort = true - return - } - p.errors << error + p.should_abort = true + } + if p.pref.message_limit >= 0 && p.errors.len >= p.pref.message_limit { + p.should_abort = true + return } - if p.pref.output_mode == .silent { + p.errors << error + if (should_abort_after_print || p.pref.output_mode == .silent) && p.tok.kind != .eof { // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. // In the silent mode however, the parser continues to run, even though it would have stopped. Some // of the parser logic does not expect that, and may loop forever. diff --git a/vlib/v/parser/parse_text_defer_stdout_test.v b/vlib/v/parser/parse_text_defer_stdout_test.v new file mode 100644 index 000000000..3bd3647c0 --- /dev/null +++ b/vlib/v/parser/parse_text_defer_stdout_test.v @@ -0,0 +1,16 @@ +import os + +const vexe = @VEXE + +fn test_parse_text_in_stdout_mode_unwinds_caller_defer() { + tmp_dir := os.join_path(os.vtmp_dir(), 'v_parser_issue_14438') + os.mkdir_all(tmp_dir) or { panic(err) } + program_path := os.join_path(tmp_dir, 'issue_14438.v') + program := "import v.ast\nimport v.parser\nimport v.pref\n\nconst bad_source = 'fn main() {\\n\\tx := 1 +\\n}\\n'\n\nfn run_parse() {\n\tmut table := ast.new_table()\n\tmut prefs := pref.new_preferences()\n\tprefs.output_mode = .stdout\n\tdefer {\n\t\tprintln('')\n\t}\n\tfile := parser.parse_text(bad_source, 'bad.v', mut table, .skip_comments, prefs)\n\tif file.errors.len == 0 {\n\t\texit(2)\n\t}\n\tprintln('')\n}\n\nfn main() {\n\trun_parse()\n}\n" + os.write_file(program_path, program) or { panic(err) } + res := os.execute('${os.quoted_path(vexe)} run ${os.quoted_path(program_path)}') + assert res.exit_code == 0, 'expected parser.parse_text to return normally, output:\n${res.output}' + assert res.output.contains('error:'), 'expected a parser error, output:\n${res.output}' + assert res.output.contains(''), 'expected defer to run, output:\n${res.output}' + assert res.output.contains(''), 'expected execution to continue after parse_text, output:\n${res.output}' +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index f0fbc01c3..d67f05432 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -427,12 +427,18 @@ pub fn parse_files(paths []string, mut table ast.Table, pref_ &pref.Preferences) $if time_parsing ? { timers.should_print = true } + stop_after_first_error := pref_.fatal_errors + || (pref_.output_mode == .stdout && !pref_.check_only && !pref_.is_vls) unsafe { mut files := []&ast.File{cap: paths.len} for path in paths { timers.start('parse_file ${path}') - files << parse_file(path, mut table, .skip_comments, pref_) + file := parse_file(path, mut table, .skip_comments, pref_) + files << file timers.show('parse_file ${path}') + if stop_after_first_error && file.errors.len > 0 { + break + } } handle_codegen_for_multiple_files(mut files) return files -- 2.39.5