Files
valkey/tests/unit/moduleapi/hash_stringref.tcl
T
Shubham S Taple e4e2c3dcf0 Fix crash and wrong return values in the hash stringRef module API (#4504)
Three issues in the `ValkeyModule_HashSetStringRef` /
`ValkeyModule_HashHasStringRef` path:

1. NULL dereference. `hashTypeHasStringRef()` used `hashtableFindRef()`
and dereferenced the returned pointer unconditionally.
`hashtableFindRef()` returns NULL when the field is not present, so
`ValkeyModule_HashHasStringRef()` on a non-existing (or expired) field
crashed the server.

2. HashHasStringRef reported false positives. For a missing key or a
non-hash key the function returned VALKEYMODULE_ERR, which is 1 — the
exact same value it returns when the field does hold a string reference.
A module could not tell "error" from "yes". Since this is a boolean
predicate, there is no room for a third status value, so it now returns
plain 0/1 and never an error code.

3. HashSetStringRef leaked internal status codes. It returned
hashTypeUpdateAsStringRef()'s C_OK/C_ERR directly, so failures surfaced
to modules as -1 instead of VALKEYMODULE_ERR (1). The status is now
mapped explicitly.

## Compatibility
ValkeyModule_HashHasStringRef changes its error return from
VALKEYMODULE_ERR to 0. This is
a behavior change for the API.

---------

Signed-off-by: Shubham S Taple <shubham.s.taple@gmail.com>
2026-10-14 12:00:41 -07:00

68 lines
2.1 KiB
Tcl

set testmodule [file normalize tests/modules/hash_stringref.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Module hash set} {
r del k
set status [catch {r hash.set_stringref k f hello} errmsg]
assert {$status == 1}
r hset k f hello1
assert_equal "0" [r hash.has_stringref k f]
r hash.set_stringref k f hello1
assert_equal "hello1" [r hget k f]
assert_equal "1" [r hash.has_stringref k f]
}
test {Module hash set_stringref on a field which does not exist} {
r del k
r hset k f hello1
set status [catch {r hash.set_stringref k nofield hello2} errmsg]
assert {$status == 1}
assert_equal "hello1" [r hget k f]
assert_equal "0" [r hash.has_stringref k f]
r hash.set_stringref k f hello1
assert_equal "hello1" [r hget k f]
assert_equal "1" [r hash.has_stringref k f]
assert_equal "0" [r hash.has_stringref k nofield]
}
test {Module hash has_stringref on a field which does not exist} {
r del k
r hset k f hello1
assert_encoding listpack k
assert_equal "0" [r hash.has_stringref k nofield]
r hash.set_stringref k f hello1
assert_encoding hashtable k
assert_equal "1" [r hash.has_stringref k f]
assert_equal "0" [r hash.has_stringref k nofield]
}
test {Module hash has_stringref on an expired field} {
r del k
r hset k f1 hello1 f2 hello2
r hash.set_stringref k f1 hello1
r hash.set_stringref k f2 hello2
r hpexpire k 1 FIELDS 1 f1
wait_for_condition 50 100 {
[r hash.has_stringref k f1] eq 0
} else {
fail "Hash field did not expire"
}
assert_equal "1" [r hash.has_stringref k f2]
}
test {Module hash has_stringref on a missing key or a non hash key} {
r del k
assert_equal "0" [r hash.has_stringref k f]
r lpush k v
assert_equal "0" [r hash.has_stringref k f]
r del k
}
test "Unload the module - hash" {
assert_equal {OK} [r module unload hash.stringref]
}
}