Skip to content

Commit

Permalink
vlib: enable more satnitized memleak detection runs without false pos…
Browse files Browse the repository at this point in the history
…itives on the CI (vlang#23200)
  • Loading branch information
felipensp authored Dec 20, 2024
1 parent 903e349 commit b077219
Show file tree
Hide file tree
Showing 16 changed files with 89 additions and 22 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/run_sanitizers_leak.suppressions
Original file line number Diff line number Diff line change
@@ -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
22 changes: 11 additions & 11 deletions .github/workflows/sanitized_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ concurrency:
jobs:
tests-sanitize-undefined-clang:
runs-on: ubuntu-20.04
timeout-minutes: 240
timeout-minutes: 120
env:
VFLAGS: -cc clang
VJOBS: 1
Expand All @@ -101,7 +101,7 @@ jobs:

tests-sanitize-undefined-gcc:
runs-on: ubuntu-20.04
timeout-minutes: 240
timeout-minutes: 120
env:
VFLAGS: -cc gcc
VJOBS: 1
Expand All @@ -126,7 +126,7 @@ jobs:

tests-sanitize-address-clang:
runs-on: ubuntu-20.04
timeout-minutes: 240
timeout-minutes: 300
env:
VFLAGS: -cc clang
VJOBS: 1
Expand All @@ -146,16 +146,16 @@ jobs:
- name: Recompile V with -cstrict
run: ./v -cg -cstrict -o v cmd/v
- 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)
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)
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:
runs-on: windows-2019
timeout-minutes: 240
timeout-minutes: 30
env:
VFLAGS: -cc msvc
VJOBS: 1
Expand All @@ -179,7 +179,7 @@ jobs:

tests-sanitize-address-gcc:
runs-on: ubuntu-20.04
timeout-minutes: 240
timeout-minutes: 300
env:
VFLAGS: -cc gcc
VJOBS: 1
Expand All @@ -199,13 +199,13 @@ jobs:
- name: Recompile V with -cstrict
run: ./v -cg -cstrict -o v cmd/v
- 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)
run:
./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)
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:
runs-on: ubuntu-20.04
Expand Down
3 changes: 3 additions & 0 deletions vlib/builtin/backtraces_nix.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
output = output.replace(' (discriminator', ': (d.')
eprintln('${output:-55s} | ${addr:14s} | ${beforeaddr}')
}
if sframes.len > 0 {
unsafe { C.free(csymbols) }
}
}
}
}
Expand Down
13 changes: 11 additions & 2 deletions vlib/compress/compress.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ pub fn compress(data []u8, flags int) ![]u8 {
if u64(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
Expand All @@ -40,5 +44,10 @@ pub fn decompress(data []u8, flags int) ![]u8 {
if u64(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
}
}
5 changes: 5 additions & 0 deletions vlib/crypto/ecdsa/ecdsa.v
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,8 @@ pub fn (pub_key PublicKey) equal(other PublicKey) bool {
res := C.EC_POINT_cmp(group, point1, point2, ctx)
return res == 0
}

// Clear allocated memory for key
pub fn key_free(ec_key &C.EC_KEY) {
C.EC_KEY_free(ec_key)
}
19 changes: 19 additions & 0 deletions vlib/crypto/ecdsa/ecdsa_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ fn test_ecdsa() {
is_valid := pub_key.verify(message, signature) or { panic(err) }
println('Signature valid: ${is_valid}')
assert is_valid
key_free(priv_key.key)
}

fn test_generate_key() ! {
// Test key generation
pub_key, priv_key := generate_key() or { panic(err) }
assert pub_key.key != unsafe { nil }
assert priv_key.key != unsafe { nil }
key_free(priv_key.key)
}

fn test_new_key_from_seed() ! {
Expand All @@ -27,6 +29,7 @@ fn test_new_key_from_seed() ! {
priv_key := new_key_from_seed(seed) or { panic(err) }
retrieved_seed := priv_key.seed() or { panic(err) }
assert seed == retrieved_seed
key_free(priv_key.key)
}

fn test_sign_and_verify() ! {
Expand All @@ -36,13 +39,15 @@ fn test_sign_and_verify() ! {
signature := priv_key.sign(message) or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid
key_free(priv_key.key)
}

fn test_seed() ! {
// Test retrieving the seed from a private key
_, priv_key := generate_key() or { panic(err) }
seed := priv_key.seed() or { panic(err) }
assert seed.len > 0
key_free(priv_key.key)
}

fn test_public_key() ! {
Expand All @@ -51,6 +56,9 @@ fn test_public_key() ! {
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2, _ := generate_key() or { panic(err) }
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() ! {
Expand All @@ -59,6 +67,8 @@ fn test_private_key_equal() ! {
seed := priv_key1.seed() or { panic(err) }
priv_key2 := new_key_from_seed(seed) or { panic(err) }
assert priv_key1.equal(priv_key2)
key_free(priv_key1.key)
key_free(priv_key2.key)
}

fn test_public_key_equal() ! {
Expand All @@ -67,6 +77,9 @@ fn test_public_key_equal() ! {
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2 := priv_key.public_key() or { panic(err) }
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() ! {
Expand All @@ -78,6 +91,8 @@ fn test_sign_with_new_key_from_seed() ! {
pub_key := priv_key.public_key() or { panic(err) }
is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid
key_free(priv_key.key)
key_free(pub_key.key)
}

fn test_invalid_signature() ! {
Expand All @@ -88,14 +103,18 @@ fn test_invalid_signature() ! {
result := pub_key.verify(message, invalid_signature) or {
// Expecting verification to fail
assert err.msg() == 'Failed to verify signature'
key_free(pub_key.key)
return
}
assert !result
key_free(pub_key.key)
}

fn test_different_keys_not_equal() ! {
// Test that different keys are not equal
_, priv_key1 := generate_key() or { panic(err) }
_, priv_key2 := generate_key() or { panic(err) }
assert !priv_key1.equal(priv_key2)
key_free(priv_key1.key)
key_free(priv_key2.key)
}
4 changes: 3 additions & 1 deletion vlib/db/sqlite/parent_child_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct Baby {
}

fn test_main() {
db := sqlite.connect(':memory:')!
mut db := sqlite.connect(':memory:')!

sql db {
create table Parent
Expand Down Expand Up @@ -80,4 +80,6 @@ fn test_main() {
assert parent[0].children[0].id == 1
assert parent[0].children[1].id == 2
assert parent[0].children.len == 2

db.close()!
}
3 changes: 2 additions & 1 deletion vlib/db/sqlite/sqlite_comptime_field_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn records_by_field[T](db sqlite.DB, fieldname string, value string) ![]T {
}

fn test_main() {
db := sqlite.connect(':memory:')!
mut db := sqlite.connect(':memory:')!
sql db {
create table Blog
}!
Expand All @@ -40,4 +40,5 @@ fn test_main() {
return
}
assert rows.len == 1
db.close()!
}
7 changes: 5 additions & 2 deletions vlib/db/sqlite/sqlite_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub:
type Content = []u8 | string

struct Host {
pub:
pub mut:
db Connection
}

Expand Down Expand Up @@ -92,7 +92,8 @@ fn test_can_access_sqlite_result_consts() {
}

fn test_alias_db() {
create_host(sqlite.connect(':memory:')!)!
mut host := create_host(sqlite.connect(':memory:')!)!
host.db.close()!
assert true
}

Expand All @@ -110,7 +111,9 @@ fn test_exec_param_many() {
'Sam',
]) or {
assert err.code() == 19 // constraint failure
db.close()!
return
}
db.close()!
assert false
}
2 changes: 2 additions & 0 deletions vlib/json/cjson/cjson_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ fn test_object_with_null() {
root.add_item_to_object('name', cjson.create_string('Andre'))
root.add_item_to_object('age', cjson.create_null())
assert root.print_unformatted() == '{"name":"Andre","age":null}'
unsafe { cjson.delete(root) }
}

fn test_creating_complex_json() {
Expand All @@ -18,4 +19,5 @@ fn test_creating_complex_json() {
println(result)

assert result == '["user",{"username":"foo","password":"bar"}]'
unsafe { cjson.delete(root) }
}
6 changes: 5 additions & 1 deletion vlib/json/cjson/cjson_wrapper.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ fn C.cJSON_Print(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
Expand Down Expand Up @@ -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.
pub fn (mut obj Node) print_unformatted() string {
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.
Expand Down
1 change: 1 addition & 0 deletions vlib/net/websocket/websocket_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import net
import net.websocket
import time

@[heap]
struct WebsocketTestResults {
pub mut:
nr_messages int
Expand Down
3 changes: 2 additions & 1 deletion vlib/v/tests/fns/fn_literal_type_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const const_users_offset = 1
const const_users_offset2 = 1

fn test_orm() {
db := sqlite.connect(':memory:') or { panic(err) }
mut db := sqlite.connect(':memory:') or { panic(err) }

upper_1 := User{
name: 'Test'
Expand All @@ -31,4 +31,5 @@ fn test_orm() {
} or { panic(err) }

assert result[0].name == 'Test'
db.close()!
}
3 changes: 2 additions & 1 deletion vlib/v/tests/orm_sub_struct_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct SubStruct {
}

fn test_orm_sub_structs() {
db := sqlite.connect(':memory:') or { panic(err) }
mut db := sqlite.connect(':memory:') or { panic(err) }
sql db {
create table Upper
}!
Expand All @@ -34,4 +34,5 @@ fn test_orm_sub_structs() {
}!

assert uppers.first().sub.name == upper_1.sub.name
db.close()!
}
4 changes: 3 additions & 1 deletion vlib/v/tests/orm_table_name_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ struct ORMTableSpecificName {
}

fn test_orm_table_name() {
db := sqlite.connect(':memory:') or { panic(err) }
mut db := sqlite.connect(':memory:') or { panic(err) }
r := sql db {
select from ORMTableSpecificName
} or {
db.close()!
assert true
return
}
db.close()!
assert false
}
3 changes: 2 additions & 1 deletion vlib/v/tests/sql_statement_inside_fn_call_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn x(m Movie) int {
}

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

0 comments on commit b077219

Please sign in to comment.