Skip to content

Commit

Permalink
parser: fix infinite loop in Parser.sql stmt in `-silent -print-watch…
Browse files Browse the repository at this point in the history
…ed-files` mode (used by `v watch`) (#20873)
  • Loading branch information
spytheman committed Feb 19, 2024
1 parent d198a89 commit efa98d9
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 35 deletions.
4 changes: 4 additions & 0 deletions vlib/v/parser/orm.v
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt {
mut lines := []ast.SqlStmtLine{}

for p.tok.kind != .rcbr {
if p.tok.kind == .eof {
p.unexpected_with_pos(pos, got: 'eof, while parsing an SQL statement')
return ast.SqlStmt{}
}
lines << p.parse_sql_stmt_line()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module main

import db.sqlite

@[table: 'modules']
struct Module {
id int @[primary; sql: serial]
name string
nr_downloads int @[sql: u64]
creator User
}

struct User {
id int @[primary; sql: serial]
age u32 @[unique: 'user']
name string @[sql: 'username'; sql_type: 'VARCHAR(200)'; unique]
is_customer bool @[sql: 'abc'; unique: 'user']
skipped_string string @[skip]
}

struct Parent {
id int @[primary; sql: serial]
name string
children []Child @[fkey: 'parent_id']
}

struct Child {
id int @[primary; sql: serial]
parent_id int
name string
}

fn main() {
eprintln('------------ ${@METHOD} -----------------')
mut db := sqlite.connect(':memory:')!
defer {
sql db {
drop table Parent
drop table Child
} or {}
db.close() or {}
}

sql db {
create table Parent
}!
sql db {
create table Child
}!
par := Parent{
name: 'test'
children: [
Child{
name: 'abc'
},
Child{
name: 'def'
},
]
}
sql db {
insert par into Parent
}!
sql db {
select from Parent where id == 1 & name == "whatever"
}!
eprintln(parent)
}
Empty file.
80 changes: 45 additions & 35 deletions vlib/v/parser/v_parser_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import v.checker
import v.pref
import term
import os
import benchmark

const vroot = os.dir(os.dir(os.dir(os.dir(@FILE))))

fn test_eval() {
/*
Expand Down Expand Up @@ -60,42 +63,40 @@ fn test_eval() {
return
}

fn test_parse_file() {
if true {
return
}
s := '
fn test_parse_text() {
println(@LOCATION)
source_text := '
fn foo() int {
f := 23
return 10+4
return 10+4+f
}
fn ff(x int) {}
12 + 3
x := 10
5+7
8+4
fn main() {
ff(12 + 3)
x := 10
bar(5+7)
ff(8+x)
}
'
table := ast.new_table()
vpref := &pref.Preferences{}
mut prog := parse_file(s, table, .skip_comments, vpref)
mut prog := parse_text(source_text, '', table, .skip_comments, vpref)
mut checker_ := checker.new_checker(table, vpref)
checker_.check(mut prog)
res, _, _, _ := c.gen([prog], table, vpref)
println(res)
}

fn test_one() {
if true {
return
}
println('\n\ntest_one()')
println(@LOCATION)
input := ['a := 10', 'b := -a', 'c := 20']
expected := 'int a = 10;int b = -a;int c = 20;'
table := ast.new_table()
vpref := &pref.Preferences{}
scope := &ast.Scope{
start_pos: 0
parent: 0
}
mut e := []ast.Stmt{}
for line in input {
Expand All @@ -113,13 +114,15 @@ fn test_one() {
println(res)
ok := expected == res
println(res)
dump(ok)
assert ok
if !ok {
}
// exit(0)
}

fn test_parse_expr() {
println(@LOCATION)
if true {
return
}
Expand All @@ -138,7 +141,6 @@ fn test_parse_expr() {
mut chk := checker.new_checker(table, vpref)
scope := &ast.Scope{
start_pos: 0
parent: 0
}
for s in input {
println('\n\nst="${s}"')
Expand Down Expand Up @@ -176,6 +178,7 @@ fn test_parse_expr() {
}

fn test_num_literals() {
println(@LOCATION)
inputs := [
'a := -1',
'b := -12.e17',
Expand All @@ -185,7 +188,6 @@ fn test_num_literals() {
table := ast.new_table()
mut scope := &ast.Scope{
start_pos: 0
parent: 0
}
mut rhs_types := []string{}
for input in inputs {
Expand Down Expand Up @@ -220,6 +222,7 @@ for s in text_expr {
*/

fn test_fn_is_html_open_tag() {
println(@LOCATION)
mut s := '<style>'
mut b := is_html_open_tag('style', s)
assert b == true
Expand All @@ -230,44 +233,47 @@ fn test_fn_is_html_open_tag() {

s = '<style/>'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = 'styl'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = 'style'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<style'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<<style>'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<style>>'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<stylex>'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<html>'
b = is_html_open_tag('style', s)
assert b == false
assert !b

s = '<script>'
b = is_html_open_tag('style', s)
assert b == false
assert !b
}

fn scan_v(mut files []string, path string) ! {
for i in os.ls(path)! {
p := os.join_path(path, i)
if !os.exists(p) {
continue
}
if os.is_file(p) {
if i.ends_with('.v') && !i.contains_any_substr(['_test.', 'test_', 'tests_']) {
files << p
Expand All @@ -281,26 +287,30 @@ fn scan_v(mut files []string, path string) ! {
}

fn parse(output_mode pref.OutputMode) ! {
mut b := benchmark.start()
mut files := []string{}
// mode_files := os.walk_ext(os.join_path(vroot, 'vlib/v/parser/testdata/${output_mode}'), '.vv')
// files << mode_files
scan_v(mut files, os.join_path(parser.vroot, 'vlib'))!
scan_v(mut files, os.join_path(parser.vroot, 'cmd'))!
mut pref_ := pref.new_preferences()
pref_.output_mode = output_mode
mut files := []string{}
scan_v(mut files, pref_.vlib)!
scan_v(mut files, os.join_path(pref_.vroot, 'cmd'))!
for f in files {
for idx, f in files {
// eprintln('> parsing in mode: ${output_mode}, ${idx+1:5}/${files.len} $f ...')
table := ast.new_table()
p := parse_file(f, table, .parse_comments, pref_)
assert isnil(p) == false
assert !isnil(p), 'failed to parse `${f}` in mode: ${output_mode}'
assert p.errors.len == 0, 'file ${f} should have been parsed with 0 errors'
}
b.measure('parsing ${files.len} files in ${output_mode} mode')
}

fn test_parse_with_silent() {
println(@LOCATION)
parse(.silent)!
}

fn test_parse_with_stdout() {
println(@LOCATION)
parse(.stdout)!
}

// For issue #15516
fn test_anon_struct() {
}
30 changes: 30 additions & 0 deletions vlib/v/parser/v_print_v_files_works_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os

const vexe = @VEXE
const vroot = os.dir(vexe)

fn test_print_v_files_in_stdout_mode() {
check_parsing_files_in_folder('vlib/v/parser/testdata/stdout', '-print-v-files')
}

//

fn test_print_v_files_in_silent_mode() {
check_parsing_files_in_folder('vlib/v/parser/testdata/silent', '-silent -print-v-files')
}

fn test_print_watched_files_in_silent_mode__used_by_vwatch() {
check_parsing_files_in_folder('vlib/v/parser/testdata/silent', '-silent -print-watched-files')
}

fn check_parsing_files_in_folder(folder string, options string) {
println('> checking .vv files in folder: `${folder}`, with `${options}` ...')
files := os.walk_ext(os.join_path(vroot, folder), '.vv')
for f in files {
cmd := '${os.quoted_path(vexe)} ${options} ${os.quoted_path(f)}'
// eprintln('> cmd: $cmd')
res := os.execute(cmd)
assert res.exit_code == 0, 'failed cmd: ${cmd}, output:\n${res.output}'
assert res.output.split_into_lines().len > 10, 'there should be several files printed by cmd: ${cmd}'
}
}

0 comments on commit efa98d9

Please sign in to comment.