vweb: check function and route parameter count (#6761)

pull/6779/head
pancake 2020-11-08 09:14:24 +01:00 committed by GitHub
parent 6da8454b3b
commit 2994e7150f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 15 deletions

View File

@ -8,7 +8,7 @@ module http
#include "vschannel.c" #include "vschannel.c"
fn C.new_tls_context() C.TlsContext fn C.new_tls_context() C.TlsContext
fn (req &Request) ssl_do(port int, method Method, host_name, path string) ?Response { fn (req &Request) ssl_do(port int, method Method, host_name string, path string) ?Response {
mut ctx := C.new_tls_context() mut ctx := C.new_tls_context()
C.vschannel_init(&ctx) C.vschannel_init(&ctx)
mut buff := malloc(C.vsc_init_resp_buff_size) mut buff := malloc(C.vsc_init_resp_buff_size)

View File

@ -8,7 +8,7 @@ module http
#include <urlmon.h> #include <urlmon.h>
fn download_file_with_progress(url, out string, cb, cb_finished voidptr) { fn download_file_with_progress(url string, out string, cb voidptr, cb_finished voidptr) {
} }
/* /*

View File

@ -260,6 +260,7 @@ pub:
receiver Field receiver Field
receiver_pos token.Position receiver_pos token.Position
is_method bool is_method bool
method_idx int
rec_mut bool // is receiver mutable rec_mut bool // is receiver mutable
rec_share table.ShareType rec_share table.ShareType
language table.Language language table.Language
@ -275,6 +276,7 @@ pub mut:
stmts []Stmt stmts []Stmt
return_type table.Type return_type table.Type
comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl
source_file &File = 0
} }
// break, continue // break, continue

View File

@ -31,7 +31,7 @@ pub struct Checker {
pref &pref.Preferences // Preferences shared from V struct pref &pref.Preferences // Preferences shared from V struct
pub mut: pub mut:
table &table.Table table &table.Table
file ast.File file &ast.File = 0
nr_errors int nr_errors int
nr_warnings int nr_warnings int
errors []errors.Error errors []errors.Error
@ -61,6 +61,7 @@ mut:
error_details []string error_details []string
generic_funcs []&ast.FnDecl generic_funcs []&ast.FnDecl
vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path* vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path*
vweb_gen_types []table.Type // vweb route checks
} }
pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker { pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
@ -71,7 +72,7 @@ pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
} }
} }
pub fn (mut c Checker) check(ast_file ast.File) { pub fn (mut c Checker) check(ast_file &ast.File) {
c.file = ast_file c.file = ast_file
for i, ast_import in ast_file.imports { for i, ast_import in ast_file.imports {
for j in 0 .. i { for j in 0 .. i {
@ -112,7 +113,7 @@ pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) {
} }
// not used right now // not used right now
pub fn (mut c Checker) check2(ast_file ast.File) []errors.Error { pub fn (mut c Checker) check2(ast_file &ast.File) []errors.Error {
c.file = ast_file c.file = ast_file
for stmt in ast_file.stmts { for stmt in ast_file.stmts {
c.stmt(stmt) c.stmt(stmt)
@ -147,6 +148,7 @@ pub fn (mut c Checker) check_files(ast_files []ast.File) {
has_main_fn = true has_main_fn = true
} }
} }
c.verify_all_vweb_routes()
// Make sure fn main is defined in non lib builds // Make sure fn main is defined in non lib builds
if c.pref.build_mode == .build_module || c.pref.is_test { if c.pref.build_mode == .build_module || c.pref.is_test {
return return
@ -4207,17 +4209,17 @@ fn (mut c Checker) post_process_generic_fns() {
// Loop thru each generic function concrete type. // Loop thru each generic function concrete type.
// Check each specific fn instantiation. // Check each specific fn instantiation.
for i in 0 .. c.generic_funcs.len { for i in 0 .. c.generic_funcs.len {
mut node := c.generic_funcs[i]
if c.table.fn_gen_types.len == 0 { if c.table.fn_gen_types.len == 0 {
// no concrete types, so just skip: // no concrete types, so just skip:
continue continue
} }
// eprintln('>> post_process_generic_fns $c.file.path | $node.name , c.table.fn_gen_types.len: $c.table.fn_gen_types.len') mut node := c.generic_funcs[i]
for gen_type in c.table.fn_gen_types[node.name] { for gen_type in c.table.fn_gen_types[node.name] {
c.cur_generic_type = gen_type c.cur_generic_type = gen_type
// sym:=c.table.get_type_symbol(gen_type)
// println('\ncalling check for $node.name for type $sym.source_name')
c.fn_decl(mut node) c.fn_decl(mut node)
if node.name in ['vweb.run_app', 'vweb.run'] {
c.vweb_gen_types << gen_type
}
} }
c.cur_generic_type = 0 c.cur_generic_type = 0
c.generic_funcs[i] = 0 c.generic_funcs[i] = 0
@ -4266,6 +4268,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.error('cannot define new methods on non-local `$sym.source_name` (' + c.error('cannot define new methods on non-local `$sym.source_name` (' +
'current module is `$c.mod`, `$sym.source_name` is from `$sym.mod`)', node.pos) 'current module is `$c.mod`, `$sym.source_name` is from `$sym.mod`)', node.pos)
} }
// needed for proper error reporting during vweb route checking
sym.methods[node.method_idx].source_fn = voidptr(node)
} }
if node.language == .v { if node.language == .v {
// Make sure all types are valid // Make sure all types are valid
@ -4322,6 +4326,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.error('missing return at end of function `$node.name`', node.pos) c.error('missing return at end of function `$node.name`', node.pos)
} }
c.returns = false c.returns = false
node.source_file = c.file
} }
fn has_top_return(stmts []ast.Stmt) bool { fn has_top_return(stmts []ast.Stmt) bool {
@ -4342,3 +4347,45 @@ fn has_top_return(stmts []ast.Stmt) bool {
} }
return false return false
} }
fn (mut c Checker) verify_vweb_params_for_method(m table.Fn) (bool, int, int) {
margs := m.params.len - 1 // first arg is the receiver/this
if m.attrs.len == 0 {
// allow non custom routed methods, with 1:1 mapping
return true, -1, margs
}
mut route_attributes := 0
for a in m.attrs {
if a.name.starts_with('/') {
route_attributes += a.name.count(':')
}
}
return route_attributes == margs, route_attributes, margs
}
fn (mut c Checker) verify_all_vweb_routes() {
if c.vweb_gen_types.len == 0 {
return
}
typ_vweb_result := c.table.find_type_idx('vweb.Result')
for vgt in c.vweb_gen_types {
sym_app := c.table.get_type_symbol(vgt)
for m in sym_app.methods {
if m.return_type_source_name == 'vweb.Result' {
is_ok, nroute_attributes, nargs := c.verify_vweb_params_for_method(m)
if !is_ok {
f := &ast.FnDecl(m.source_fn)
if isnil(f) {
continue
}
if f.return_type == typ_vweb_result &&
f.receiver.typ == m.params[0].typ && f.name == m.name {
c.file = f.source_file // setup of file path for the warning
c.warn('mismatched parameters count between vweb method `${sym_app.name}.$m.name` ($nargs) and route attribute $m.attrs ($nroute_attributes)',
f.pos)
}
}
}
}
}
}

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/vweb_routing_checks.vv:22:1: error: mismatched parameters count between vweb method `App.bar` (1) and route attribute ['/bar'] (0)
20 | // segfault because path taks 0 vars and fcn takes 1 arg
21 | ['/bar']
22 | pub fn (mut app App) bar(a string) vweb.Result {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 | app.vweb.html('works')
24 | return vweb.Result{}
vlib/v/checker/tests/vweb_routing_checks.vv:29:1: error: mismatched parameters count between vweb method `App.cow` (0) and route attribute ['/cow/:low'] (1)
27 | // no segfault, but it shouldnt compile
28 | ['/cow/:low']
29 | pub fn (mut app App) cow() vweb.Result {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 | app.vweb.html('works')
31 | return vweb.Result{}

View File

@ -0,0 +1,50 @@
import vweb
struct App {
pub mut:
vweb vweb.Context
}
pub fn (mut app App) no_attributes(a string) vweb.Result {
return vweb.Result{}
}
// works fine, as long as fcn gets 1 arg and route takes 1 var
['/foo/:bar']
pub fn (mut app App) foo(a string) vweb.Result {
eprintln('foo')
app.vweb.html('works')
return vweb.Result{}
}
// segfault because path taks 0 vars and fcn takes 1 arg
['/bar']
pub fn (mut app App) bar(a string) vweb.Result {
app.vweb.html('works')
return vweb.Result{}
}
// no segfault, but it shouldnt compile
['/cow/:low']
pub fn (mut app App) cow() vweb.Result {
app.vweb.html('works')
return vweb.Result{}
}
pub fn (app App) init_once() {
//
}
pub fn (app App) init() {
//
}
pub fn (mut app App) index() {
app.vweb.html('hello')
}
fn main() {
port := 8181
mut app := App{}
vweb.run_app<App>(mut app, port)
}

View File

@ -258,15 +258,15 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
mut return_type := table.void_type mut return_type := table.void_type
if p.tok.kind.is_start_of_type() || if p.tok.kind.is_start_of_type() ||
(p.tok.kind == .key_fn && p.tok.line_nr == p.prev_tok.line_nr) { (p.tok.kind == .key_fn && p.tok.line_nr == p.prev_tok.line_nr) {
end_pos = p.tok.position()
return_type = p.parse_type() return_type = p.parse_type()
} }
mut type_sym_method_idx := 0
// Register // Register
if is_method { if is_method {
mut type_sym := p.table.get_type_symbol(rec_type) mut type_sym := p.table.get_type_symbol(rec_type)
ret_type_sym := p.table.get_type_symbol(return_type) ret_type_sym := p.table.get_type_symbol(return_type)
// p.warn('reg method $type_sym.name . $name ()') // p.warn('reg method $type_sym.name . $name ()')
type_sym.register_method(table.Fn{ type_sym_method_idx = type_sym.register_method(table.Fn{
name: name name: name
params: params params: params
return_type: return_type return_type: return_type
@ -307,6 +307,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
language: language language: language
}) })
} }
end_pos = p.prev_tok.position()
// Body // Body
p.cur_fn_name = name p.cur_fn_name = name
mut stmts := []ast.Stmt{} mut stmts := []ast.Stmt{}
@ -336,6 +337,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
} }
receiver_pos: receiver_pos receiver_pos: receiver_pos
is_method: is_method is_method: is_method
method_idx: type_sym_method_idx
rec_mut: rec_mut rec_mut: rec_mut
language: language language: language
no_body: no_body no_body: no_body

View File

@ -38,6 +38,7 @@ pub:
attrs []Attr attrs []Attr
pub mut: pub mut:
name string name string
source_fn voidptr // set in the checker, while processing fn declarations
} }
fn (f &Fn) method_equals(o &Fn) bool { fn (f &Fn) method_equals(o &Fn) bool {
@ -162,9 +163,12 @@ pub fn (mut t Table) register_fn(new_fn Fn) {
t.fns[new_fn.name] = new_fn t.fns[new_fn.name] = new_fn
} }
pub fn (mut t TypeSymbol) register_method(new_fn Fn) { pub fn (mut t TypeSymbol) register_method(new_fn Fn) int {
// returns a method index, stored in the ast.FnDecl
// for faster lookup in the checker's fn_decl method
// println('reg me $new_fn.name nr_args=$new_fn.args.len') // println('reg me $new_fn.name nr_args=$new_fn.args.len')
t.methods << new_fn t.methods << new_fn
return t.methods.len - 1
} }
pub fn (t &Table) register_aggregate_method(mut sym TypeSymbol, name string) ?Fn { pub fn (t &Table) register_aggregate_method(mut sym TypeSymbol, name string) ?Fn {

View File

@ -485,7 +485,11 @@ fn handle_conn<T>(conn net.Socket, mut app T) {
// search again for method // search again for method
if action == method.name && method.attrs.len > 0 { if action == method.name && method.attrs.len > 0 {
// call action method // call action method
app.$method(vars) if method.args.len == vars.len {
app.$method(vars)
} else {
eprintln('warning: uneven parameters count (${method.args.len}) in `$method.name`, compared to the vweb route `$method.attrs` (${vars.len})')
}
} }
} }
} }