vlib: enable more satnitized memleak detection runs without false positives on the CI (#23200)

This commit is contained in:
Felipe Pena 2024-12-20 11:09:16 -03:00 committed by GitHub
parent 903e349a7c
commit b0772193f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 89 additions and 22 deletions

View File

@ -0,0 +1,13 @@
# websocket tests
leak:mbedtls_ssl_setup
# sqlite tests
leak:*sqlite*
# v
leak:v__*
leak:os__*
leak:string__plus
leak:malloc_uncollectable
leak:new_array_from_c_array
leak:_vinit

View File

@ -75,7 +75,7 @@ concurrency:
jobs: jobs:
tests-sanitize-undefined-clang: tests-sanitize-undefined-clang:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 240 timeout-minutes: 120
env: env:
VFLAGS: -cc clang VFLAGS: -cc clang
VJOBS: 1 VJOBS: 1
@ -101,7 +101,7 @@ jobs:
tests-sanitize-undefined-gcc: tests-sanitize-undefined-gcc:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 240 timeout-minutes: 120
env: env:
VFLAGS: -cc gcc VFLAGS: -cc gcc
VJOBS: 1 VJOBS: 1
@ -126,7 +126,7 @@ jobs:
tests-sanitize-address-clang: tests-sanitize-address-clang:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 240 timeout-minutes: 300
env: env:
VFLAGS: -cc clang VFLAGS: -cc clang
VJOBS: 1 VJOBS: 1
@ -146,16 +146,16 @@ jobs:
- name: Recompile V with -cstrict - name: Recompile V with -cstrict
run: ./v -cg -cstrict -o v cmd/v run: ./v -cg -cstrict -o v cmd/v
- name: Self tests (-fsanitize=address) - name: Self tests (-fsanitize=address)
run: ASAN_OPTIONS=detect_leaks=0 ./v -cflags "-fsanitize=address,pointer-compare,pointer-subtract" test-self vlib run: ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v -cflags "-fsanitize=address,pointer-compare,pointer-subtract" test-self vlib
- name: Self tests (V compiled with -fsanitize=address) - name: Self tests (V compiled with -fsanitize=address)
run: ./v -cflags -fsanitize=address -o v cmd/v && run: ./v -cflags -fsanitize=address -o v cmd/v &&
ASAN_OPTIONS=detect_leaks=0 ./v -cc tcc test-self -asan-compiler vlib ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v -cc tcc test-self -asan-compiler vlib
- name: Build examples (V compiled with -fsanitize=address) - name: Build examples (V compiled with -fsanitize=address)
run: ASAN_OPTIONS=detect_leaks=0 ./v build-examples run: ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v build-examples
tests-sanitize-address-msvc: tests-sanitize-address-msvc:
runs-on: windows-2019 runs-on: windows-2019
timeout-minutes: 240 timeout-minutes: 30
env: env:
VFLAGS: -cc msvc VFLAGS: -cc msvc
VJOBS: 1 VJOBS: 1
@ -179,7 +179,7 @@ jobs:
tests-sanitize-address-gcc: tests-sanitize-address-gcc:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 240 timeout-minutes: 300
env: env:
VFLAGS: -cc gcc VFLAGS: -cc gcc
VJOBS: 1 VJOBS: 1
@ -199,13 +199,13 @@ jobs:
- name: Recompile V with -cstrict - name: Recompile V with -cstrict
run: ./v -cg -cstrict -o v cmd/v run: ./v -cg -cstrict -o v cmd/v
- name: Self tests (-fsanitize=address) - name: Self tests (-fsanitize=address)
run: ASAN_OPTIONS=detect_leaks=0 ./v -cflags -fsanitize=address test-self vlib run: ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v -cflags -fsanitize=address test-self vlib
- name: Self tests (V compiled with -fsanitize=address) - name: Self tests (V compiled with -fsanitize=address)
run: run:
./v -cflags -fsanitize=address,pointer-compare,pointer-subtract -o v cmd/v && ./v -cflags -fsanitize=address,pointer-compare,pointer-subtract -o v cmd/v &&
ASAN_OPTIONS=detect_leaks=0 ./v -cc tcc test-self -asan-compiler vlib ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v -cc tcc test-self -asan-compiler vlib
- name: Build examples (V compiled with -fsanitize=address) - name: Build examples (V compiled with -fsanitize=address)
run: ASAN_OPTIONS=detect_leaks=0 ./v build-examples run: ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 LSAN_OPTIONS=max_leaks=1:print_suppressions=0:suppressions=.github/workflows/run_sanitizers_leak.suppressions ./v build-examples
tests-sanitize-memory-clang: tests-sanitize-memory-clang:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@ -106,6 +106,9 @@ fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
output = output.replace(' (discriminator', ': (d.') output = output.replace(' (discriminator', ': (d.')
eprintln('${output:-55s} | ${addr:14s} | ${beforeaddr}') eprintln('${output:-55s} | ${addr:14s} | ${beforeaddr}')
} }
if sframes.len > 0 {
unsafe { C.free(csymbols) }
}
} }
} }
} }

View File

@ -24,7 +24,11 @@ pub fn compress(data []u8, flags int) ![]u8 {
if u64(out_len) > max_size { if u64(out_len) > max_size {
return error('compressed data is too large (${out_len} > ${max_size})') return error('compressed data is too large (${out_len} > ${max_size})')
} }
return unsafe { address.vbytes(int(out_len)) } unsafe {
ret := address.vbytes(int(out_len)).clone()
C.free(address)
return ret
}
} }
// decompresses an array of bytes based on providing flags and returns the decompressed bytes in a new array // decompresses an array of bytes based on providing flags and returns the decompressed bytes in a new array
@ -40,5 +44,10 @@ pub fn decompress(data []u8, flags int) ![]u8 {
if u64(out_len) > max_size { if u64(out_len) > max_size {
return error('decompressed data is too large (${out_len} > ${max_size})') return error('decompressed data is too large (${out_len} > ${max_size})')
} }
return unsafe { address.vbytes(int(out_len)) }
unsafe {
ret := address.vbytes(int(out_len)).clone()
C.free(address)
return ret
}
} }

View File

@ -227,3 +227,8 @@ pub fn (pub_key PublicKey) equal(other PublicKey) bool {
res := C.EC_POINT_cmp(group, point1, point2, ctx) res := C.EC_POINT_cmp(group, point1, point2, ctx)
return res == 0 return res == 0
} }
// Clear allocated memory for key
pub fn key_free(ec_key &C.EC_KEY) {
C.EC_KEY_free(ec_key)
}

View File

@ -12,6 +12,7 @@ fn test_ecdsa() {
is_valid := pub_key.verify(message, signature) or { panic(err) } is_valid := pub_key.verify(message, signature) or { panic(err) }
println('Signature valid: ${is_valid}') println('Signature valid: ${is_valid}')
assert is_valid assert is_valid
key_free(priv_key.key)
} }
fn test_generate_key() ! { fn test_generate_key() ! {
@ -19,6 +20,7 @@ fn test_generate_key() ! {
pub_key, priv_key := generate_key() or { panic(err) } pub_key, priv_key := generate_key() or { panic(err) }
assert pub_key.key != unsafe { nil } assert pub_key.key != unsafe { nil }
assert priv_key.key != unsafe { nil } assert priv_key.key != unsafe { nil }
key_free(priv_key.key)
} }
fn test_new_key_from_seed() ! { fn test_new_key_from_seed() ! {
@ -27,6 +29,7 @@ fn test_new_key_from_seed() ! {
priv_key := new_key_from_seed(seed) or { panic(err) } priv_key := new_key_from_seed(seed) or { panic(err) }
retrieved_seed := priv_key.seed() or { panic(err) } retrieved_seed := priv_key.seed() or { panic(err) }
assert seed == retrieved_seed assert seed == retrieved_seed
key_free(priv_key.key)
} }
fn test_sign_and_verify() ! { fn test_sign_and_verify() ! {
@ -36,6 +39,7 @@ fn test_sign_and_verify() ! {
signature := priv_key.sign(message) or { panic(err) } signature := priv_key.sign(message) or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) } is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid assert is_valid
key_free(priv_key.key)
} }
fn test_seed() ! { fn test_seed() ! {
@ -43,6 +47,7 @@ fn test_seed() ! {
_, priv_key := generate_key() or { panic(err) } _, priv_key := generate_key() or { panic(err) }
seed := priv_key.seed() or { panic(err) } seed := priv_key.seed() or { panic(err) }
assert seed.len > 0 assert seed.len > 0
key_free(priv_key.key)
} }
fn test_public_key() ! { fn test_public_key() ! {
@ -51,6 +56,9 @@ fn test_public_key() ! {
pub_key1 := priv_key.public_key() or { panic(err) } pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2, _ := generate_key() or { panic(err) } pub_key2, _ := generate_key() or { panic(err) }
assert !pub_key1.equal(pub_key2) assert !pub_key1.equal(pub_key2)
key_free(priv_key.key)
key_free(pub_key1.key)
key_free(pub_key2.key)
} }
fn test_private_key_equal() ! { fn test_private_key_equal() ! {
@ -59,6 +67,8 @@ fn test_private_key_equal() ! {
seed := priv_key1.seed() or { panic(err) } seed := priv_key1.seed() or { panic(err) }
priv_key2 := new_key_from_seed(seed) or { panic(err) } priv_key2 := new_key_from_seed(seed) or { panic(err) }
assert priv_key1.equal(priv_key2) assert priv_key1.equal(priv_key2)
key_free(priv_key1.key)
key_free(priv_key2.key)
} }
fn test_public_key_equal() ! { fn test_public_key_equal() ! {
@ -67,6 +77,9 @@ fn test_public_key_equal() ! {
pub_key1 := priv_key.public_key() or { panic(err) } pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2 := priv_key.public_key() or { panic(err) } pub_key2 := priv_key.public_key() or { panic(err) }
assert pub_key1.equal(pub_key2) assert pub_key1.equal(pub_key2)
key_free(priv_key.key)
key_free(pub_key1.key)
key_free(pub_key2.key)
} }
fn test_sign_with_new_key_from_seed() ! { fn test_sign_with_new_key_from_seed() ! {
@ -78,6 +91,8 @@ fn test_sign_with_new_key_from_seed() ! {
pub_key := priv_key.public_key() or { panic(err) } pub_key := priv_key.public_key() or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) } is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid assert is_valid
key_free(priv_key.key)
key_free(pub_key.key)
} }
fn test_invalid_signature() ! { fn test_invalid_signature() ! {
@ -88,9 +103,11 @@ fn test_invalid_signature() ! {
result := pub_key.verify(message, invalid_signature) or { result := pub_key.verify(message, invalid_signature) or {
// Expecting verification to fail // Expecting verification to fail
assert err.msg() == 'Failed to verify signature' assert err.msg() == 'Failed to verify signature'
key_free(pub_key.key)
return return
} }
assert !result assert !result
key_free(pub_key.key)
} }
fn test_different_keys_not_equal() ! { fn test_different_keys_not_equal() ! {
@ -98,4 +115,6 @@ fn test_different_keys_not_equal() ! {
_, priv_key1 := generate_key() or { panic(err) } _, priv_key1 := generate_key() or { panic(err) }
_, priv_key2 := generate_key() or { panic(err) } _, priv_key2 := generate_key() or { panic(err) }
assert !priv_key1.equal(priv_key2) assert !priv_key1.equal(priv_key2)
key_free(priv_key1.key)
key_free(priv_key2.key)
} }

View File

@ -20,7 +20,7 @@ struct Baby {
} }
fn test_main() { fn test_main() {
db := sqlite.connect(':memory:')! mut db := sqlite.connect(':memory:')!
sql db { sql db {
create table Parent create table Parent
@ -80,4 +80,6 @@ fn test_main() {
assert parent[0].children[0].id == 1 assert parent[0].children[0].id == 1
assert parent[0].children[1].id == 2 assert parent[0].children[1].id == 2
assert parent[0].children.len == 2 assert parent[0].children.len == 2
db.close()!
} }

View File

@ -22,7 +22,7 @@ fn records_by_field[T](db sqlite.DB, fieldname string, value string) ![]T {
} }
fn test_main() { fn test_main() {
db := sqlite.connect(':memory:')! mut db := sqlite.connect(':memory:')!
sql db { sql db {
create table Blog create table Blog
}! }!
@ -40,4 +40,5 @@ fn test_main() {
return return
} }
assert rows.len == 1 assert rows.len == 1
db.close()!
} }

View File

@ -11,7 +11,7 @@ pub:
type Content = []u8 | string type Content = []u8 | string
struct Host { struct Host {
pub: pub mut:
db Connection db Connection
} }
@ -92,7 +92,8 @@ fn test_can_access_sqlite_result_consts() {
} }
fn test_alias_db() { fn test_alias_db() {
create_host(sqlite.connect(':memory:')!)! mut host := create_host(sqlite.connect(':memory:')!)!
host.db.close()!
assert true assert true
} }
@ -110,7 +111,9 @@ fn test_exec_param_many() {
'Sam', 'Sam',
]) or { ]) or {
assert err.code() == 19 // constraint failure assert err.code() == 19 // constraint failure
db.close()!
return return
} }
db.close()!
assert false assert false
} }

View File

@ -5,6 +5,7 @@ fn test_object_with_null() {
root.add_item_to_object('name', cjson.create_string('Andre')) root.add_item_to_object('name', cjson.create_string('Andre'))
root.add_item_to_object('age', cjson.create_null()) root.add_item_to_object('age', cjson.create_null())
assert root.print_unformatted() == '{"name":"Andre","age":null}' assert root.print_unformatted() == '{"name":"Andre","age":null}'
unsafe { cjson.delete(root) }
} }
fn test_creating_complex_json() { fn test_creating_complex_json() {
@ -18,4 +19,5 @@ fn test_creating_complex_json() {
println(result) println(result)
assert result == '["user",{"username":"foo","password":"bar"}]' assert result == '["user",{"username":"foo","password":"bar"}]'
unsafe { cjson.delete(root) }
} }

View File

@ -82,6 +82,8 @@ fn C.cJSON_Print(object &C.cJSON) &char
fn C.cJSON_PrintUnformatted(object &C.cJSON) &char fn C.cJSON_PrintUnformatted(object &C.cJSON) &char
fn C.cJSON_free(voidptr)
// //
// version returns the version of cJSON as a string // version returns the version of cJSON as a string
@ -168,7 +170,9 @@ pub fn (mut obj Node) print() string {
// print serialises the node to a string, without formatting its structure, so the resulting string is shorter/cheaper to transmit. // print serialises the node to a string, without formatting its structure, so the resulting string is shorter/cheaper to transmit.
pub fn (mut obj Node) print_unformatted() string { pub fn (mut obj Node) print_unformatted() string {
mut s := C.cJSON_PrintUnformatted(obj) mut s := C.cJSON_PrintUnformatted(obj)
return unsafe { tos3(s) } ret := unsafe { tos_clone(&u8(s)) }
C.cJSON_free(s)
return ret
} }
// str returns the unformatted serialisation to string of the given Node. // str returns the unformatted serialisation to string of the given Node.

View File

@ -3,6 +3,7 @@ import net
import net.websocket import net.websocket
import time import time
@[heap]
struct WebsocketTestResults { struct WebsocketTestResults {
pub mut: pub mut:
nr_messages int nr_messages int

View File

@ -10,7 +10,7 @@ const const_users_offset = 1
const const_users_offset2 = 1 const const_users_offset2 = 1
fn test_orm() { fn test_orm() {
db := sqlite.connect(':memory:') or { panic(err) } mut db := sqlite.connect(':memory:') or { panic(err) }
upper_1 := User{ upper_1 := User{
name: 'Test' name: 'Test'
@ -31,4 +31,5 @@ fn test_orm() {
} or { panic(err) } } or { panic(err) }
assert result[0].name == 'Test' assert result[0].name == 'Test'
db.close()!
} }

View File

@ -11,7 +11,7 @@ struct SubStruct {
} }
fn test_orm_sub_structs() { fn test_orm_sub_structs() {
db := sqlite.connect(':memory:') or { panic(err) } mut db := sqlite.connect(':memory:') or { panic(err) }
sql db { sql db {
create table Upper create table Upper
}! }!
@ -34,4 +34,5 @@ fn test_orm_sub_structs() {
}! }!
assert uppers.first().sub.name == upper_1.sub.name assert uppers.first().sub.name == upper_1.sub.name
db.close()!
} }

View File

@ -6,12 +6,14 @@ struct ORMTableSpecificName {
} }
fn test_orm_table_name() { fn test_orm_table_name() {
db := sqlite.connect(':memory:') or { panic(err) } mut db := sqlite.connect(':memory:') or { panic(err) }
r := sql db { r := sql db {
select from ORMTableSpecificName select from ORMTableSpecificName
} or { } or {
db.close()!
assert true assert true
return return
} }
db.close()!
assert false assert false
} }

View File

@ -10,7 +10,7 @@ fn x(m Movie) int {
} }
fn test_sql_statement_inside_fn_call() { fn test_sql_statement_inside_fn_call() {
db := sqlite.connect(':memory:') or { panic('failed') } mut db := sqlite.connect(':memory:') or { panic('failed') }
sql db { sql db {
create table Movie create table Movie
}! }!
@ -21,4 +21,5 @@ fn test_sql_statement_inside_fn_call() {
dump(x(sql db { dump(x(sql db {
select from Movie where id == 1 select from Movie where id == 1
}!.first())) }!.first()))
db.close()!
} }