diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ef2b3161e..aeceb2f1d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -216,16 +216,16 @@ jobs: # github-token: ${{ secrets.GITHUB_TOKEN }} - ubuntu-autofree-selfcompile: - runs-on: ubuntu-18.04 - env: - VFLAGS: -cc gcc - steps: - - uses: actions/checkout@v2 - - name: Build V - run: make -j4 - - name: V self compilation with -autofree - run: ./v -o v2 -autofree cmd/v && ./v2 -o v3 -autofree cmd/v && ./v3 -o v4 -autofree cmd/v +# ubuntu-autofree-selfcompile: +# runs-on: ubuntu-18.04 +# env: +# VFLAGS: -cc gcc +# steps: +# - uses: actions/checkout@v2 +# - name: Build V +# run: make -j4 +# - name: V self compilation with -autofree +# run: ./v -o v2 -autofree cmd/v && ./v2 -o v3 -autofree cmd/v && ./v3 -o v4 -autofree cmd/v # Ubuntu docker pre-built container @@ -490,16 +490,16 @@ jobs: - name: Run autobahn services run: docker-compose -f ${{github.workspace}}/vlib/x/websocket/tests/autobahn/docker-compose.yml up -d - name: Build client test - run: docker exec autobahn_client "v" "/src/tests/autobahn/autobahn_client.v" + run: docker exec autobahn_client "v" "/src/tests/autobahn/autobahn_client.v" - name: Run client test - run: docker exec autobahn_client "/src/tests/autobahn/autobahn_client" + run: docker exec autobahn_client "/src/tests/autobahn/autobahn_client" - name: Run server test - run: docker exec autobahn_server "wstest" "-m" "fuzzingclient" "-s" "/config/fuzzingclient.json" + run: docker exec autobahn_server "wstest" "-m" "fuzzingclient" "-s" "/config/fuzzingclient.json" - name: Copy reports run: docker cp autobahn_server:/reports ${{github.workspace}}/reports - name: Test success run: docker exec autobahn_server "python" "/check_results.py" - + - name: Publish all reports uses: actions/upload-artifact@v2 with: @@ -515,4 +515,4 @@ jobs: with: name: server path: ${{github.workspace}}/reports/servers/index.html - + diff --git a/vlib/net/urllib/urllib.v b/vlib/net/urllib/urllib.v index b1358263c7..a13907cc66 100644 --- a/vlib/net/urllib/urllib.v +++ b/vlib/net/urllib/urllib.v @@ -153,52 +153,52 @@ fn unescape(s_ string, mode EncodingMode) ?string { for i := 0; i < s.len; { x := s[i] match x { - `%` { - if s == '' { - break - } - n++ - if i + 2 >= s.len || !ishex(s[i + 1]) || !ishex(s[i + 2]) { - s = s[i..] - if s.len > 3 { - s = s[..3] - } - return error(error_msg(err_msg_escape, s)) - } - // Per https://tools.ietf.org/html/rfc3986#page-21 - // in the host component %-encoding can only be used - // for non-ASCII bytes. - // But https://tools.ietf.org/html/rfc6874#section-2 - // introduces %25 being allowed to escape a percent sign - // in IPv6 scoped-address literals. Yay. - if mode == .encode_host && unhex(s[i + 1]) < 8 && s[i..i + 3] != '%25' { - return error(error_msg(err_msg_escape, s[i..i + 3])) - } - if mode == .encode_zone { - // RFC 6874 says basically 'anything goes' for zone identifiers - // and that even non-ASCII can be redundantly escaped, - // but it seems prudent to restrict %-escaped bytes here to those - // that are valid host name bytes in their unescaped form. - // That is, you can use escaping in the zone identifier but not - // to introduce bytes you couldn't just write directly. - // But Windows puts spaces here! Yay. - v := ( (unhex(s[i + 1])<= s.len || !ishex(s[i + 1]) || !ishex(s[i + 2]) { + s = s[i..] + if s.len > 3 { + s = s[..3] } - i++ - }} + return error(error_msg(err_msg_escape, s)) + } + // Per https://tools.ietf.org/html/rfc3986#page-21 + // in the host component %-encoding can only be used + // for non-ASCII bytes. + // But https://tools.ietf.org/html/rfc6874#section-2 + // introduces %25 being allowed to escape a percent sign + // in IPv6 scoped-address literals. Yay. + if mode == .encode_host && unhex(s[i + 1]) < 8 && s[i..i + 3] != '%25' { + return error(error_msg(err_msg_escape, s[i..i + 3])) + } + if mode == .encode_zone { + // RFC 6874 says basically 'anything goes' for zone identifiers + // and that even non-ASCII can be redundantly escaped, + // but it seems prudent to restrict %-escaped bytes here to those + // that are valid host name bytes in their unescaped form. + // That is, you can use escaping in the zone identifier but not + // to introduce bytes you couldn't just write directly. + // But Windows puts spaces here! Yay. + v := ( (unhex(s[i + 1])< 0 && !call_expr.args[0].typ.has_flag(.optional) + if free_tmp_arg_vars { + for i, arg in call_expr.args { + if arg.typ != table.string_type { + continue + } + if arg.expr is ast.Ident || arg.expr is ast.StringLiteral { + continue + } + call_expr.args[i].is_tmp_autofree = true + } } - return c.call_fn(call_expr) + return typ } fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_expr ast.CallExpr) { @@ -2228,7 +2240,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { } ast.Module { c.mod = node.name - c.is_builtin_mod = node.name == 'builtin' + c.is_builtin_mod = node.name in ['builtin', 'os', 'strconv'] c.check_valid_snake_case(node.name, 'module name', node.pos) } ast.Return { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index c743153178..926784a3d0 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -53,7 +53,8 @@ mut: file ast.File fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 last_fn_c_name string - tmp_count int + tmp_count int // counter for unique tmp vars (_tmp1, tmp2 etc) + tmp_count2 int // a separate tmp var counter for autofree fn calls variadic_args map[string]int is_c_call bool // e.g. `C.printf("v")` is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) @@ -106,9 +107,10 @@ mut: comptime_var_type_map map[string]table.Type match_sumtype_exprs []ast.Expr match_sumtype_syms []table.TypeSymbol - tmp_arg_vars_to_free []string + // tmp_arg_vars_to_free []string called_fn_name string cur_mod string + is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` } const ( @@ -663,6 +665,12 @@ pub fn (mut g Gen) new_tmp_var() string { return '_t$g.tmp_count' } +/* +pub fn (mut g Gen) new_tmp_var2() string { + g.tmp_count2++ + return '_tt$g.tmp_count2' +} +*/ pub fn (mut g Gen) reset_tmp_count() { g.tmp_count = 0 } @@ -717,8 +725,9 @@ fn (mut g Gen) write_v_source_line_info(pos token.Position) { fn (mut g Gen) stmt(node ast.Stmt) { g.stmt_path_pos << g.out.len + /* defer { - // If have temporary string exprs to free after this statement, do it. e.g.: + // If we have temporary string exprs to free after this statement, do it. e.g.: // `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);` if g.pref.autofree { if g.strs_to_free != '' { @@ -727,6 +736,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { } } } + */ // println('cgen.stmt()') // g.writeln('//// stmt start') match node { @@ -932,7 +942,8 @@ fn (mut g Gen) stmt(node ast.Stmt) { // definitions are sorted and added in write_types } ast.Module { - g.is_builtin_mod = node.name == 'builtin' + // g.is_builtin_mod = node.name == 'builtin' + g.is_builtin_mod = node.name in ['builtin', 'os', 'strconv'] g.cur_mod = node.name } ast.Return { diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 25152418ac..3a2414b318 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -340,7 +340,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { g.write('${dot}_object') if node.args.len > 0 { g.write(', ') - g.call_args(node.args, node.expected_arg_types) + // g.call_args(node.args, node.expected_arg_types) // , []) + g.call_args(node) } g.write(')') return @@ -449,7 +450,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } */ // /////// - g.call_args(node.args, node.expected_arg_types) + // g.call_args(node.args, node.expected_arg_types) // , []) + g.call_args(node) g.write(')') } @@ -485,7 +487,8 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { encode_name := c_name(name) + '_' + util.no_dots(json_type_str) g.writeln('// json.encode') g.write('cJSON* $json_obj = ${encode_name}(') - g.call_args(node.args, node.expected_arg_types) + // g.call_args(node.args, node.expected_arg_types) // , []) + g.call_args(node) g.writeln(');') tmp2 = g.new_tmp_var() g.writeln('string $tmp2 = json__json_print($json_obj);') @@ -499,7 +502,10 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { g.write('cJSON* $json_obj = json__json_parse(') // Skip the first argument in json.decode which is a type // its name was already used to generate the function call - g.call_args(node.args[1..], node.expected_arg_types) + // g.call_args(node.args[1..], node.expected_arg_types) // , []) + g.is_js_call = true + g.call_args(node) + g.is_js_call = false g.writeln(');') tmp2 = g.new_tmp_var() g.writeln('Option_$typ $tmp2 = $fn_name ($json_obj);') @@ -522,27 +528,26 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { } // Create a temporary var for each argument in order to free it (only if it's a complex expression, // like `foo(get_string())` or `foo(a + b)` - free_tmp_arg_vars := g.autofree && g.pref.experimental && !g.is_builtin_mod && g.cur_mod != - 'os' && node.args.len > 0 && !node.args[0].typ.has_flag(.optional) + mut free_tmp_arg_vars := g.autofree && g.pref.experimental && !g.is_builtin_mod && + node.args.len > 0 && !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v + mut cur_line := '' if free_tmp_arg_vars { - g.tmp_arg_vars_to_free = []string{cap: 10} // TODO perf + free_tmp_arg_vars = false // set the flag to true only if we have at least one arg to free + g.tmp_count2++ for i, arg in node.args { - if arg.typ != table.string_type { + if !arg.is_tmp_autofree { continue } - if arg.expr is ast.Ident || arg.expr is ast.StringLiteral { - continue - } - t := g.new_tmp_var() + '_arg_expr_${name}_$i' // g.new_tmp_var() + free_tmp_arg_vars = true + // t := g.new_tmp_var() + '_arg_expr_${name}_$i' + fn_name := node.name.replace('.', '_') + t := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' g.called_fn_name = name - /* - g.write('string $t = ') - g.expr(arg.expr) - g.writeln('; // to free $i ') - */ str_expr := g.write_expr_to_string(arg.expr) - g.insert_before_stmt('string $t = $str_expr; // new. to free $i ') - g.tmp_arg_vars_to_free << t // i + // g.insert_before_stmt('string $t = $str_expr; // new. to free $i ') + cur_line = g.go_before_stmt(0) + // println('cur line ="$cur_line"') + g.writeln('string $t = $str_expr; // new. to free $i ') } } // Handle `print(x)` @@ -602,31 +607,48 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { } else if g.pref.is_debug && node.name == 'panic' { paline, pafile, pamod, pafn := g.panic_debug_info(node.pos) g.write('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), ') - g.call_args(node.args, node.expected_arg_types) + // g.call_args(node.args, node.expected_arg_types) // , []) + g.call_args(node) g.write(')') } else { + // Simple function call + if free_tmp_arg_vars { + // g.writeln(';') + g.write(cur_line + ' /* cur line*/') + } g.write('${g.get_ternary_name(name)}(') if g.is_json_fn { g.write(json_obj) } else { - g.call_args(node.args, node.expected_arg_types) + // g.call_args(node.args, node.expected_arg_types) // , tmp_arg_vars_to_free) + g.call_args(node) } g.write(')') } g.is_c_call = false g.is_json_fn = false - if free_tmp_arg_vars && g.tmp_arg_vars_to_free.len > 0 { + if free_tmp_arg_vars { // && tmp_arg_vars_to_free.len > 0 { + // g.writeln(';') + // g.write(cur_line + ' /* cur line*/') + // g.write(tmp) // Now free the tmp arg vars right after the function call g.writeln(';') - for tmp in g.tmp_arg_vars_to_free { - g.writeln('string_free(&$tmp);') + for i, arg in node.args { + if arg.is_tmp_autofree { + fn_name := node.name.replace('.', '_') + tmp := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + g.writeln('string_free(&$tmp);') + } } g.writeln('') - g.tmp_arg_vars_to_free.clear() } } -fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type) { +// fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type, tmp_arg_vars_to_free []string) { +// fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type) { +fn (mut g Gen) call_args(node ast.CallExpr) { + args := if g.is_js_call { node.args[1..] } else { node.args } + expected_types := node.expected_arg_types is_variadic := expected_types.len > 0 && expected_types[expected_types.len - 1].has_flag(.variadic) is_forwarding_varg := args.len > 0 && args[args.len - 1].typ.has_flag(.variadic) gen_vargs := is_variadic && !is_forwarding_varg @@ -635,7 +657,8 @@ fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type) { break } use_tmp_var_autofree := g.autofree && g.pref.experimental && arg.typ == table.string_type && - g.tmp_arg_vars_to_free.len > 0 + arg.is_tmp_autofree + // g.write('/* af=$arg.is_tmp_autofree */') mut is_interface := false // some c fn definitions dont have args (cfns.v) or are not updated in checker // when these are fixed we wont need this check @@ -654,14 +677,14 @@ fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type) { if is_interface { g.expr(arg.expr) } else if use_tmp_var_autofree { - for name in g.tmp_arg_vars_to_free { + if arg.is_tmp_autofree { // We saved expressions in temp variables so that they can be freed later. // `foo(str + str2) => x := str + str2; foo(x); x.free()` // g.write('_arg_expr_${g.called_fn_name}_$i') // Use these variables here. - if name.contains('_$i') { - g.write(name) - } + fn_name := node.name.replace('.', '_') + name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + g.write('/*af arg*/' + name) } } else { g.ref_or_deref_arg(arg, expected_types[i]) @@ -669,11 +692,9 @@ fn (mut g Gen) call_args(args []ast.CallArg, expected_types []table.Type) { } else { if use_tmp_var_autofree { // TODO copypasta, move to an inline fn - for name in g.tmp_arg_vars_to_free { - if name.contains('_$i') { - g.write(name) - } - } + fn_name := node.name.replace('.', '_') + name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + g.write('/*af arg2*/' + name) } else { g.expr(arg.expr) } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c8d07c04ef..cc6f7e1fed 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -40,7 +40,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp } } p.check(.lpar) - args := p.call_args() + mut args := p.call_args() last_pos := p.tok.position() p.check(.rpar) // ! in mutable methods diff --git a/vlib/v/tests/valgrind/1.strings_and_arrays.v b/vlib/v/tests/valgrind/1.strings_and_arrays.v index 35cb135436..d255fd0b45 100644 --- a/vlib/v/tests/valgrind/1.strings_and_arrays.v +++ b/vlib/v/tests/valgrind/1.strings_and_arrays.v @@ -15,12 +15,17 @@ fn foo() { // nums.free() // this should result in a double free and a CI error } -fn handle_strings(s, p string) { +fn handle_strings(s, p string) int { + return 0 +} + +fn handle_int(n int) { } fn str_tmp_expr() { println('a' + 'b') // tmp expression result must be freed - handle_strings('c' + 'd', 'e' + 'f') + handle_strings('c' + 'd', 'e' + 'f') // multiple tmp expressions must be freed + // handle_int(handle_strings('x' + 'y', 'f')) // exprs 2 levels deep must bee freed } struct Foo {