[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