[PATCH 7/X] libsanitizer: Add tests
Richard Sandiford
richard.sandiford@arm.com
Fri Nov 20 19:14:29 GMT 2020
Matthew Malcomson <matthew.malcomson@arm.com> writes:
> Adding hwasan tests.
>
> Only interesting thing here is that we have to make sure the tagging mechanism
> is deterministic to avoid flaky tests.
Sorry for not reviewing this one earlier. TBH I only spot-checked
the tests themselves (they look good). But on hwasan-dg.exp: I think
we should try to avoid so much cut-&-paste between asan-dg.exp and
hwasan-dg.exp.
For one thing (and obviously not your fault), it seems odd to me that
check_effective_target_fsanitize_address is defined in asan-dg.exp.
I think it and the new check_effective_targets* should be defined
in target-supports.exp instead. On:
> +proc check_effective_target_hwaddress_exec {} {
> + if ![check_runtime hwaddress_exec {
> + int main (void) { return 0; }
> + }] {
> + return 0;
> + }
> + return 1;
> +
> + # hwasan doesn't work if there's a ulimit on virtual memory.
> + if ![is_remote target] {
> + if [catch {exec sh -c "ulimit -v"} ulimit_v] {
> + # failed to get ulimit
> + } elseif [regexp {^[0-9]+$} $ulimit_v] {
> + # ulimit -v gave a numeric limit
> + warning "skipping hwasan tests due to ulimit -v"
> + return 0;
> + }
> + }
> +}
either the “hwasan doesn't work” block or the early “return 1” should
be removed. (I'm guessing the former.)
> +proc hwasan_include_flags {} {
> + global srcdir
> + global TESTING_IN_BUILD_TREE
> +
> + set flags ""
> +
> + if { [is_remote host] || ! [info exists TESTING_IN_BUILD_TREE] } {
> + return "${flags}"
> + }
> +
> + set flags "-I$srcdir/../../libsanitizer/include"
> +
> + return "$flags"
> +}
This is identical to the asan version, but I guess it's small enough
that the cut-&-paste doesn't matter.
> +
> +#
> +# hwasan_link_flags -- compute library path and flags to find libhwasan.
> +# (originally from g++.exp)
> +#
> +
> +proc hwasan_link_flags { paths } {
> + global srcdir
> + global ld_library_path
> + global shlib_ext
> + global hwasan_saved_library_path
> +
> + set gccpath ${paths}
> + set flags ""
> +
> + set shlib_ext [get_shlib_extension]
> + set hwasan_saved_library_path $ld_library_path
> +
> + if { $gccpath != "" } {
> + if { [file exists "${gccpath}/libsanitizer/hwasan/.libs/libhwasan.a"]
> + || [file exists "${gccpath}/libsanitizer/hwasan/.libs/libhwasan.${shlib_ext}"] } {
> + append flags " -B${gccpath}/libsanitizer/ "
> + append flags " -B${gccpath}/libsanitizer/hwasan/ "
> + append flags " -L${gccpath}/libsanitizer/hwasan/.libs "
> + append ld_library_path ":${gccpath}/libsanitizer/hwasan/.libs"
> + }
> + } else {
> + global tool_root_dir
> +
> + set libhwasan [lookfor_file ${tool_root_dir} libhwasan]
> + if { $libhwasan != "" } {
> + append flags "-L${libhwasan} "
> + append ld_library_path ":${libhwasan}"
> + }
> + }
> +
> + set_ld_library_path_env_vars
> +
> + return "$flags"
> +}
Here I'd suggest:
- In asan-dg.exp, have:
# Compute library path and flags to find libsanitizer library LIB.
# (originally from g++.exp).
proc asan_link_flags_1 { paths lib } {
…body…
}
…existing comment…
proc asan_link_flags { paths } {
return [asan_link_flags_1 $paths asan]
}
where …body… is more or less the current body of asan_link_flags with
“asan” replaced by ${lib}. E.g.:
global ${lib}_saved_library_path
…
set ${lib}_saved_library_path $ld_library_path
is fine. For local variables like:
set libasan [lookfor_file ${tool_root_dir} libasan]
if { $libasan != "" } {
append flags "-L${libasan} "
append ld_library_path ":${libasan}"
}
it would make more sense to use a generic name instead, e.g.:
set libdir [lookfor_file ${tool_root_dir} lib${lib}]
if { $libdir != "" } {
append flags "-L${libdir} "
append ld_library_path ":${libdir}"
}
- Have hwasan-dg.exp include asan-dg.exp.
- Have:
proc hwasan_link_flags { paths } {
return [asan_link_flags_1 $paths hwasan]
}
> +
> +#
> +# hwasan_init -- called at the start of each subdir of tests
> +#
> +
> +proc hwasan_init { args } {
> + global TEST_ALWAYS_FLAGS
> + global ALWAYS_CXXFLAGS
> + global TOOL_OPTIONS
> + global hwasan_saved_TEST_ALWAYS_FLAGS
> + global hwasan_saved_ALWAYS_CXXFLAGS
> +
> + setenv HWASAN_OPTIONS "random_tags=0"
> +
> + set link_flags ""
> + if ![is_remote host] {
> + if [info exists TOOL_OPTIONS] {
> + set link_flags "[hwasan_link_flags [get_multilibs ${TOOL_OPTIONS}]]"
> + } else {
> + set link_flags "[hwasan_link_flags [get_multilibs]]"
> + }
> + }
> +
> + set include_flags "[hwasan_include_flags]"
> +
> + if [info exists TEST_ALWAYS_FLAGS] {
> + set hwasan_saved_TEST_ALWAYS_FLAGS $TEST_ALWAYS_FLAGS
> + }
> + if [info exists ALWAYS_CXXFLAGS] {
> + set hwasan_saved_ALWAYS_CXXFLAGS $ALWAYS_CXXFLAGS
> + set ALWAYS_CXXFLAGS [concat "{ldflags=$link_flags}" $ALWAYS_CXXFLAGS]
> + set ALWAYS_CXXFLAGS [concat "{additional_flags=-fsanitize=hwaddress --param hwasan-random-frame-tag=0 -g $include_flags}" $ALWAYS_CXXFLAGS]
> + } else {
> + if [info exists TEST_ALWAYS_FLAGS] {
> + set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=hwaddress --param hwasan-random-frame-tag=0 -g $include_flags $TEST_ALWAYS_FLAGS"
> + } else {
> + set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=hwaddress --param hwasan-random-frame-tag=0 -g $include_flags"
> + }
> + }
> +}
I agree this one is different enough from the asan version that it
isn't worth commonising the code.
> +
> +#
> +# hwasan_finish -- called at the start of each subdir of tests
> +#
> +
> +proc hwasan_finish { args } {
> + global TEST_ALWAYS_FLAGS
> + global hwasan_saved_TEST_ALWAYS_FLAGS
> + global hwasan_saved_ALWAYS_CXXFLAGS
> + global hwasan_saved_library_path
> + global ld_library_path
> +
> + unsetenv HWASAN_OPTIONS
> +
> + if [info exists hwasan_saved_ALWAYS_CXXFLAGS ] {
> + set ALWAYS_CXXFLAGS $hwasan_saved_ALWAYS_CXXFLAGS
> + } else {
> + if [info exists hwasan_saved_TEST_ALWAYS_FLAGS] {
> + set TEST_ALWAYS_FLAGS $hwasan_saved_TEST_ALWAYS_FLAGS
> + } else {
> + unset TEST_ALWAYS_FLAGS
> + }
> + }
> + set ld_library_path $hwasan_saved_library_path
> + set_ld_library_path_env_vars
> + clear_effective_target_cache
> +}
And I guess then it would be more consistent to keep this separate too,
if the _init function is.
> +
> +# Symbolize lines like
> +# #2 0xdeadbeef (/some/path/libsanitizer.so.0.0.0+0xbeef)
> +# in $output using addr2line to
> +# #2 0xdeadbeef in foobar file:123
> +proc hwasan_symbolize { output } {
> + set addresses [regexp -inline -all -line "^ *#\[0-9\]+ 0x\[0-9a-f\]+ \[(\](\[^)\]+)\[+\](0x\[0-9a-f\]+)\[)\]$" "$output"]
> + if { [llength $addresses] > 0 } {
> + set addr2line_name [find_binutils_prog addr2line]
> + set idx 1
> + while { $idx < [llength $addresses] } {
> + set key [regsub -all "\[\]\[\]" [lindex $addresses $idx] "\\\\&"]
> + set val [lindex $addresses [expr $idx + 1]]
> + lappend arr($key) $val
> + set idx [expr $idx + 3]
> + }
> + foreach key [array names arr] {
> + set args "-f -e $key $arr($key)"
> + set status [remote_exec host "$addr2line_name" "$args"]
> + if { [lindex $status 0] > 0 } continue
> + regsub -all "\r\n" [lindex $status 1] "\n" addr2line_output
> + regsub -all "\[\n\r\]BFD: \[^\n\r\]*" $addr2line_output "" addr2line_output
> + regsub -all "^BFD: \[^\n\r\]*\[\n\r\]" $addr2line_output "" addr2line_output
> + set addr2line_output [regexp -inline -all -line "^\[^\n\r]*" $addr2line_output]
> + set idx 0
> + foreach val $arr($key) {
> + if { [expr $idx + 1] < [llength $addr2line_output] } {
> + set fnname [lindex $addr2line_output $idx]
> + set fileline [lindex $addr2line_output [expr $idx + 1]]
> + if { "$fnname" != "??" } {
> + set newkey "$key+$val"
> + set repl($newkey) "$fnname $fileline"
> + }
> + set idx [expr $idx + 2]
> + }
> + }
> + }
> + set idx 0
> + set new_output ""
> + while {[regexp -start $idx -indices " #\[0-9\]+ 0x\[0-9a-f\]+ \[(\](\[^)\]+\[+\]0x\[0-9a-f\]+)\[)\]" "$output" -> addr] > 0} {
> + set low [lindex $addr 0]
> + set high [lindex $addr 1]
> + set val [string range "$output" $low $high]
> + append new_output [string range "$output" $idx [expr $low - 2]]
> + if [info exists repl($val)] {
> + append new_output "in $repl($val)"
> + } else {
> + append new_output "($val)"
> + }
> + set idx [expr $high + 2]
> + }
> + append new_output [string range "$output" $idx [string length "$output"]]
> + return "$new_output"
> + }
> + return "$output"
> +}
> +
> +# Return a list of gtest tests, printed in the form
> +# DEJAGNU_GTEST_TEST AddressSanitizer_SimpleDeathTest
> +# DEJAGNU_GTEST_TEST AddressSanitizer_VariousMallocsTest
> +proc hwasan_get_gtest_test_list { output } {
> + set idx 0
> + set ret ""
> + while {[regexp -start $idx -indices "DEJAGNU_GTEST_TEST (\[^\n\r\]*)(\r\n|\n|\r)" "$output" -> testname] > 0} {
> + set low [lindex $testname 0]
> + set high [lindex $testname 1]
> + set val [string range "$output" $low $high]
> + lappend ret $val
> + set idx [expr $high + 1]
> + }
> + return $ret
> +}
> +
> +# Return a list of gtest EXPECT_DEATH tests, printed in the form
> +# DEJAGNU_GTEST_EXPECT_DEATH1 statement DEJAGNU_GTEST_EXPECT_DEATH1 regexp DEJAGNU_GTEST_EXPECT_DEATH1
> +# DEJAGNU_GTEST_EXPECT_DEATH2 other statement DEJAGNU_GTEST_EXPECT_DEATH2 other regexp DEJAGNU_GTEST_EXPECT_DEATH2
> +proc hwasan_get_gtest_expect_death_list { output } {
> + set idx 0
> + set ret ""
> + while {[regexp -start $idx -indices "DEJAGNU_GTEST_EXPECT_DEATH(\[0-9\]*)" "$output" -> id ] > 0} {
> + set low [lindex $id 0]
> + set high [lindex $id 1]
> + set val_id [string range "$output" $low $high]
> + if {[regexp -start $low -indices "$val_id (.*) DEJAGNU_GTEST_EXPECT_DEATH$val_id (.*) DEJAGNU_GTEST_EXPECT_DEATH$val_id\[\n\r\]" "$output" whole statement regexpr ] == 0} { break }
> + set low [lindex $statement 0]
> + set high [lindex $statement 1]
> + set val_statement [string range "$output" $low $high]
> + set low [lindex $regexpr 0]
> + set high [lindex $regexpr 1]
> + set val_regexpr [string range "$output" $low $high]
> + lappend ret [list "$val_id" "$val_statement" "$val_regexpr"]
> + set idx [lindex $whole 1]
> + }
> + return $ret
> +}
AFAICT these three functions are identical to the asan-dg.exp versions,
with no asan/hwasan variance. I think it would be better to reuse the
asan versions for hwasan too.
> +
> +# Replace ${tool}_load with a wrapper so that we can symbolize the output.
> +if { [info procs ${tool}_load] != [list] \
> + && [info procs saved_hwasan_${tool}_load] == [list] } {
> + rename ${tool}_load saved_hwasan_${tool}_load
> +
> + proc ${tool}_load { program args } {
> + global tool
> + global hwasan_last_gtest_test_list
> + global hwasan_last_gtest_expect_death_list
> + set result [eval [list saved_hwasan_${tool}_load $program] $args]
> + set output [lindex $result 1]
> + set symbolized_output [hwasan_symbolize "$output"]
> + set hwasan_last_gtest_test_list [hwasan_get_gtest_test_list "$output"]
> + set hwasan_last_gtest_expect_death_list [hwasan_get_gtest_expect_death_list "$output"]
> + set result [list [lindex $result 0] $symbolized_output]
> + return $result
> + }
> +}
Also, I'm not sure we need separate hwasan versions of these globals.
> +
> +# Utility for running gtest hwasan emulation under dejagnu, invoked via dg-final.
> +# Call pass if variable has the desired value, otherwise fail.
> +#
> +# Argument 0 handles expected failures and the like
> +proc hwasan-gtest { args } {
> + global tool
> + global hwasan_last_gtest_test_list
> + global hwasan_last_gtest_expect_death_list
> +
> + if { ![info exists hwasan_last_gtest_test_list] } { return }
> + if { [llength $hwasan_last_gtest_test_list] == 0 } { return }
> + if { ![isnative] || [is_remote target] } { return }
> +
> + set gtest_test_list $hwasan_last_gtest_test_list
> + unset hwasan_last_gtest_test_list
> +
> + if { [llength $args] >= 1 } {
> + switch [dg-process-target [lindex $args 0]] {
> + "S" { }
> + "N" { return }
> + "F" { setup_xfail "*-*-*" }
> + "P" { }
> + }
> + }
> +
> + # This assumes that we are three frames down from dg-test, and that
> + # it still stores the filename of the testcase in a local variable "name".
> + # A cleaner solution would require a new DejaGnu release.
> + upvar 2 name testcase
> + upvar 2 prog prog
> +
> + set output_file "[file rootname [file tail $prog]].exe"
> +
> + foreach gtest $gtest_test_list {
> + set testname "$testcase $gtest"
> + set status -1
> +
> + setenv DEJAGNU_GTEST_ARG "$gtest"
> + set result [${tool}_load ./$output_file $gtest]
> + unsetenv DEJAGNU_GTEST_ARG
> + set status [lindex $result 0]
> + set output [lindex $result 1]
> + if { "$status" == "pass" } {
> + pass "$testname execution test"
> + if { [info exists hwasan_last_gtest_expect_death_list] } {
> + set gtest_expect_death_list $hwasan_last_gtest_expect_death_list
> + foreach gtest_death $gtest_expect_death_list {
> + set id [lindex $gtest_death 0]
> + set testname "$testcase $gtest [lindex $gtest_death 1]"
> + set regexpr [lindex $gtest_death 2]
> + set status -1
> +
> + setenv DEJAGNU_GTEST_ARG "$gtest:$id"
> + set result [${tool}_load ./$output_file "$gtest:$id"]
> + unsetenv DEJAGNU_GTEST_ARG
> + set status [lindex $result 0]
> + set output [lindex $result 1]
> + if { "$status" == "fail" } {
> + pass "$testname execution test"
> + if { ![regexp $regexpr ${output}] } {
> + fail "$testname output pattern test"
> + send_log "Output should match: $regexpr\n"
> + } else {
> + pass "$testname output pattern test"
> + }
> + } elseif { "$status" == "pass" } {
> + fail "$testname execution test"
> + } else {
> + $status "$testname execution test"
> + }
> + }
> + }
> + } else {
> + $status "$testname execution test"
> + }
> + unset hwasan_last_gtest_expect_death_list
> + }
> +
> + return
> +}
And if there are no separate versions of those variables, and if
hwasan-dg.exp does include asan-dg.exp, then I think it would be
enough to replace hwasan_symbolize onwards with:
# Utility for running gtest hwasan emulation under dejagnu, invoked via dg-final.
# Call pass if variable has the desired value, otherwise fail.
#
# Argument 0 handles expected failures and the like
proc hwasan-gtest { args } {
asan-gtest {*}$args
}
Thanks,
Richard
More information about the Gcc-patches
mailing list