# Copyright (C) 2012-2014 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GCC; see the file COPYING3. If not see # . # Return 1 if compilation with -fsanitize=address is error-free for trivial # code, 0 otherwise. proc check_effective_target_faddress_sanitizer {} { return [check_no_compiler_messages faddress_sanitizer object { void foo (void) { } } "-fsanitize=address"] } proc asan_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" } # # asan_link_flags -- compute library path and flags to find libasan. # (originally from g++.exp) # proc asan_link_flags { paths } { global srcdir global ld_library_path global shlib_ext set gccpath ${paths} set flags "" set shlib_ext [get_shlib_extension] if { $gccpath != "" } { if { [file exists "${gccpath}/libsanitizer/asan/.libs/libasan.a"] || [file exists "${gccpath}/libsanitizer/asan/.libs/libasan.${shlib_ext}"] } { append flags " -B${gccpath}/libsanitizer/ " append flags " -B${gccpath}/libsanitizer/asan/ " append flags " -L${gccpath}/libsanitizer/asan/.libs " append ld_library_path ":${gccpath}/libsanitizer/asan/.libs" } } else { global tool_root_dir set libasan [lookfor_file ${tool_root_dir} libasan] if { $libasan != "" } { append flags "-L${libasan} " append ld_library_path ":${libasan}" } } set_ld_library_path_env_vars return "$flags" } # # asan_init -- called at the start of each subdir of tests # proc asan_init { args } { global TEST_ALWAYS_FLAGS global ALWAYS_CXXFLAGS global TOOL_OPTIONS global asan_saved_TEST_ALWAYS_FLAGS set link_flags "" if ![is_remote host] { if [info exists TOOL_OPTIONS] { set link_flags "[asan_link_flags [get_multilibs ${TOOL_OPTIONS}]]" } else { set link_flags "[asan_link_flags [get_multilibs]]" } } set include_flags "[asan_include_flags]" if [info exists TEST_ALWAYS_FLAGS] { set asan_saved_TEST_ALWAYS_FLAGS $TEST_ALWAYS_FLAGS } if [info exists ALWAYS_CXXFLAGS] { set ALWAYS_CXXFLAGS [concat "{ldflags=$link_flags}" $ALWAYS_CXXFLAGS] set ALWAYS_CXXFLAGS [concat "{additional_flags=-fsanitize=address -static-libasan -g $include_flags}" $ALWAYS_CXXFLAGS] } else { if [info exists TEST_ALWAYS_FLAGS] { set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=address -static-libasan -g $include_flags $TEST_ALWAYS_FLAGS" } else { set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=address -static-libasan -g $include_flags" } } if { $link_flags != "" } { return 1 } return 0 } # # asan_finish -- called at the start of each subdir of tests # proc asan_finish { args } { global TEST_ALWAYS_FLAGS global asan_saved_TEST_ALWAYS_FLAGS if [info exists asan_saved_TEST_ALWAYS_FLAGS] { set TEST_ALWAYS_FLAGS $asan_saved_TEST_ALWAYS_FLAGS } else { unset TEST_ALWAYS_FLAGS } } # 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 asan_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 asan_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 asan_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 } # Replace ${tool}_load with a wrapper so that we can symbolize the output. if { [info procs ${tool}_load] != [list] \ && [info procs saved_asan_${tool}_load] == [list] } { rename ${tool}_load saved_asan_${tool}_load proc ${tool}_load { program args } { global tool global asan_last_gtest_test_list global asan_last_gtest_expect_death_list set result [eval [list saved_asan_${tool}_load $program] $args] set output [lindex $result 1] set symbolized_output [asan_symbolize "$output"] set asan_last_gtest_test_list [asan_get_gtest_test_list "$output"] set asan_last_gtest_expect_death_list [asan_get_gtest_expect_death_list "$output"] set result [list [lindex $result 0] $symbolized_output] return $result } } # Utility for running gtest asan 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 asan-gtest { args } { global tool global asan_last_gtest_test_list global asan_last_gtest_expect_death_list if { ![info exists asan_last_gtest_test_list] } { return } if { [llength $asan_last_gtest_test_list] == 0 } { return } if { ![isnative] || [is_remote target] } { return } set gtest_test_list $asan_last_gtest_test_list unset asan_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 asan_last_gtest_expect_death_list] } { set gtest_expect_death_list $asan_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 asan_last_gtest_expect_death_list } return }