autofree: lots of fixes

pull/6314/head
Alexander Medvednikov 2020-09-05 12:00:35 +02:00
parent b015033c53
commit 3410705974
8 changed files with 186 additions and 127 deletions

View File

@ -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

View File

@ -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])<<byte(4)) | unhex(s[i + 2]))
if s[i..i + 3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
error(error_msg(err_msg_escape, s[i..i + 3]))
}
}
i += 3
`%` {
if s == '' {
break
}
`+` {
has_plus = mode == .encode_query_component
i++
}
else {
if (mode == .encode_host || mode == .encode_zone) && s[i] < 0x80 && should_escape(s[i], mode) {
error(error_msg('unescape: invalid character in host name', s[i..i + 1]))
n++
if i + 2 >= 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])<<byte(4)) | unhex(s[i + 2]))
if s[i..i + 3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
error(error_msg(err_msg_escape, s[i..i + 3]))
}
}
i += 3
}
`+` {
has_plus = mode == .encode_query_component
i++
}
else {
if (mode == .encode_host || mode == .encode_zone) && s[i] < 0x80 && should_escape(s[i], mode) {
error(error_msg('unescape: invalid character in host name', s[i..i + 1]))
}
i++
}}
}
if n == 0 && !has_plus {
return s

View File

@ -10,11 +10,11 @@ import v.errors
pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl
pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | CastExpr |
CharLiteral | ChanInit | Comment | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr |
IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr |
None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectorExpr | SizeOf |
SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf | UnsafeExpr
ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral |
Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr |
MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr |
SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type |
TypeOf | UnsafeExpr
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | CompIf | ConstDecl |
DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl |
@ -158,10 +158,10 @@ pub mut:
pub struct ConstDecl {
pub:
is_pub bool
pos token.Position
is_pub bool
pos token.Position
pub mut:
fields []ConstField
fields []ConstField
end_comments []Comment
}
@ -289,16 +289,26 @@ pub mut:
return_type table.Type
should_be_skipped bool
generic_type table.Type // TODO array, to support multiple types
// autofree_vars []AutofreeArgVar
// autofree_vars_ids []int
}
/*
pub struct AutofreeArgVar {
name string
idx int
}
*/
pub struct CallArg {
pub:
is_mut bool
share table.ShareType
expr Expr
comments []Comment
is_mut bool
share table.ShareType
expr Expr
comments []Comment
pub mut:
typ table.Type
typ table.Type
is_tmp_autofree bool // for autofree
// tmp_name string // for autofree
}
pub struct Return {
@ -761,12 +771,12 @@ pub mut:
pub struct ChanInit {
pub:
pos token.Position
cap_expr Expr
has_cap bool
pos token.Position
cap_expr Expr
has_cap bool
pub mut:
typ table.Type
elem_type table.Type
typ table.Type
elem_type table.Type
}
pub struct MapInit {
@ -1069,9 +1079,9 @@ pub fn (expr Expr) position() token.Position {
pub fn (expr Expr) is_lvalue() bool {
match expr {
Ident {return true}
IndexExpr {return expr.left.is_lvalue()}
SelectorExpr {return expr.expr.is_lvalue()}
Ident { return true }
IndexExpr { return expr.left.is_lvalue() }
SelectorExpr { return expr.expr.is_lvalue() }
else {}
}
return false

View File

@ -846,10 +846,22 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) table.Type {
c.stmts(call_expr.or_block.stmts)
if call_expr.is_method {
return c.call_method(call_expr)
typ := if call_expr.is_method { c.call_method(call_expr) } else { c.call_fn(call_expr) }
// autofree
free_tmp_arg_vars := c.pref.autofree && c.pref.experimental && !c.is_builtin_mod &&
call_expr.args.len > 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 {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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 {