Significantly change the way we build ASan unittests in CMake
authorAlexey Samsonov <samsonov@google.com>
Wed, 19 Dec 2012 12:33:39 +0000 (12:33 +0000)
committerAlexey Samsonov <samsonov@google.com>
Wed, 19 Dec 2012 12:33:39 +0000 (12:33 +0000)
build tree. Now just-built Clang is used to:
  1) compile instrumented sources (as before);
  2) compile non-instrumented sources;
  3) compile our own instrumented version of googletest;
  4) link it all together using -fsanitize=address flag
     (instead of trying to copy linker behavior in
      CMake build rules).

This makes ASan unittests pretty much self-consistent
and independent of other LLVM libraries.

llvm-svn: 170541

compiler-rt/CMakeLists.txt
compiler-rt/cmake/Modules/CompilerRTCompile.cmake [new file with mode: 0644]
compiler-rt/cmake/Modules/CompilerRTUnittests.cmake [new file with mode: 0644]
compiler-rt/lib/asan/tests/CMakeLists.txt

index 4544f15..bbf41f7 100644 (file)
@@ -15,6 +15,14 @@ include(LLVMParseArguments)
 # runtime libraries.
 cmake_minimum_required(VERSION 2.8.8)
 
+# Add path for custom modules
+set(CMAKE_MODULE_PATH
+  ${CMAKE_MODULE_PATH}
+  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules"
+  )
+
+set(COMPILER_RT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
 # FIXME: Below we assume that the target build of LLVM/Clang is x86, which is
 # not at all valid. Much of this can be fixed just by switching to use
 # a just-built-clang binary for the compiles.
diff --git a/compiler-rt/cmake/Modules/CompilerRTCompile.cmake b/compiler-rt/cmake/Modules/CompilerRTCompile.cmake
new file mode 100644 (file)
index 0000000..2794cab
--- /dev/null
@@ -0,0 +1,16 @@
+include(LLVMParseArguments)
+
+# Compile a source into an object file with just-built Clang using
+# a provided compile flags and dependenices.
+# clang_compile(<object> <source>
+#               CFLAGS <list of compile flags>
+#               DEPS <list of dependencies>)
+macro(clang_compile object_file source)
+  parse_arguments(SOURCE "CFLAGS;DEPS" "" ${ARGN})
+  get_filename_component(source_rpath ${source} REALPATH)
+  add_custom_command(
+    OUTPUT ${object_file}
+    COMMAND clang ${SOURCE_CFLAGS} -c -o "${object_file}" ${source_rpath}
+    MAIN_DEPENDENCY ${source}
+    DEPENDS clang ${SOURCE_DEPS})
+endmacro()
diff --git a/compiler-rt/cmake/Modules/CompilerRTUnittests.cmake b/compiler-rt/cmake/Modules/CompilerRTUnittests.cmake
new file mode 100644 (file)
index 0000000..96ad673
--- /dev/null
@@ -0,0 +1,30 @@
+include(AddLLVM)
+include(LLVMParseArguments)
+
+set(COMPILER_RT_GTEST_PATH ${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest)
+set(COMPILER_RT_GTEST_SOURCE ${COMPILER_RT_GTEST_PATH}/gtest-all.cc)
+set(COMPILER_RT_GTEST_INCLUDE_CFLAGS
+  -DGTEST_NO_LLVM_RAW_OSTREAM=1
+  -I${COMPILER_RT_GTEST_PATH}/include
+)
+
+# Use Clang to link objects into a single executable with just-built
+# Clang, using specific link flags. Make executable a part of provided
+# test_suite.
+# add_compiler_rt_test(<test_suite> <test_name>
+#                      OBJECTS <object files>
+#                      DEPS <deps (e.g. runtime libs)>
+#                      LINK_FLAGS <link flags>)
+macro(add_compiler_rt_test test_suite test_name)
+  parse_arguments(TEST "OBJECTS;DEPS;LINK_FLAGS" "" ${ARGN})
+  get_unittest_directory(OUTPUT_DIR)
+  set(output_bin "${OUTPUT_DIR}/${test_name}")
+  add_custom_command(
+    OUTPUT ${output_bin}
+    COMMAND clang ${TEST_OBJECTS} -o "${output_bin}"
+            ${TEST_LINK_FLAGS}
+    DEPENDS clang ${TEST_DEPS} ${TEST_OBJECTS})
+  add_custom_target(${test_name} DEPENDS ${output_bin})
+  # Make the test suite depend on the binary.
+  add_dependencies(${test_suite} ${test_name})
+endmacro()
index 44f188c..7db5af7 100644 (file)
 # instrumentation against the just-built runtime library.
 
 include(CheckCXXCompilerFlag)
+include(CompilerRTCompile)
+include(CompilerRTUnittests)
 
 include_directories(..)
 include_directories(../..)
 
 set(ASAN_UNITTEST_COMMON_CFLAGS
+  ${COMPILER_RT_GTEST_INCLUDE_CFLAGS}
+  -I${COMPILER_RT_SOURCE_DIR}/include
+  -I${COMPILER_RT_SOURCE_DIR}/lib
+  -I${COMPILER_RT_SOURCE_DIR}/lib/asan
   -Wall
   -Wno-format
   -Werror
-  -fvisibility=hidden
   -g
   -O2
 )
@@ -46,26 +51,29 @@ else()
   )
 endif()
 
+set(ASAN_LINK_FLAGS -fsanitize=address)
+if(ANDROID)
+  list(APPEND ASAN_LINK_FLAGS -pie)
+elseif(APPLE)
+  # Unit tests on Mac depend on Foundation.
+  list(APPEND ASAN_LINK_FLAGS -framework Foundation)
+endif()
+# Unit tests require libstdc++.
+list(APPEND ASAN_LINK_FLAGS -lstdc++)
+
 # Support 64-bit and 32-bit builds.
 if(LLVM_BUILD_32_BITS)
   list(APPEND ASAN_UNITTEST_COMMON_CFLAGS -m32)
+  list(APPEND ASAN_LINK_FLAGS -m32)
 else()
   list(APPEND ASAN_UNITTEST_COMMON_CFLAGS -m64)
+  list(APPEND ASAN_LINK_FLAGS -m64)
 endif()
 
-set(ASAN_GTEST_INCLUDE_CFLAGS
-  -I${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest/include
-  -I${LLVM_MAIN_SRC_DIR}/include
-  -I${LLVM_BINARY_DIR}/include
-  -D__STDC_CONSTANT_MACROS
-  -D__STDC_LIMIT_MACROS
-)
-
 set(ASAN_BLACKLIST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/asan_test.ignore")
 
 set(ASAN_UNITTEST_INSTRUMENTED_CFLAGS
   ${ASAN_UNITTEST_COMMON_CFLAGS}
-  ${ASAN_GTEST_INCLUDE_CFLAGS}
   -fsanitize=address
   -mllvm "-asan-blacklist=${ASAN_BLACKLIST_FILE}"
   -mllvm -asan-stack=1
@@ -75,103 +83,91 @@ set(ASAN_UNITTEST_INSTRUMENTED_CFLAGS
   -mllvm -asan-use-after-return=0
 )
 
-function(add_asan_test testsuite testname)
-  add_unittest(${testsuite} ${testname} ${ARGN})
-  if (APPLE)
-    # Darwin-specific linker flags.
-    set_property(TARGET ${testname} APPEND PROPERTY
-                 LINK_FLAGS "-framework Foundation")
-    target_link_libraries(${testname} clang_rt.asan_osx)
-  elseif (ANDROID)
-    target_link_libraries(${testname} clang_rt.asan-arm-android)
-  elseif (UNIX)
-    # Linux-specific linker flags.
-    set_property(TARGET ${testname} APPEND PROPERTY
-                 LINK_FLAGS "-lpthread -ldl -rdynamic")
-    if(LLVM_BUILD_32_BITS)
-      target_link_libraries(${testname} clang_rt.asan-i386)
-    else()
-      target_link_libraries(${testname} clang_rt.asan-x86_64)
-    endif()
-  endif()
-  set(add_compile_flags "")
-  get_property(compile_flags TARGET ${testname} PROPERTY COMPILE_FLAGS)
-  foreach(arg ${ASAN_UNITTEST_COMMON_CFLAGS})
-    set(add_compile_flags "${add_compile_flags} ${arg}")
-  endforeach(arg ${ASAN_UNITTEST_COMMON_CFLAGS})
-  set_property(TARGET ${testname} PROPERTY COMPILE_FLAGS
-               "${compile_flags} ${add_compile_flags}")
-endfunction()
-
-set(ASAN_NOINST_TEST_SOURCES
-  asan_noinst_test.cc
-  asan_test_main.cc
-)
+# Compile source and add it to the object list using compiler
+# options in ${ARGN}.
+macro(asan_compile obj_list source)
+  get_filename_component(basename ${source} NAME)
+  set(output_obj "${basename}.o")
+  clang_compile(${output_obj} ${source}
+                CFLAGS ${ARGN}
+                DEPS gtest ${ASAN_RUNTIME_LIBRARIES}
+                           ${ASAN_BLACKLIST_FILE})
+  list(APPEND ${obj_list} ${output_obj})
+endmacro()
+
+# Link ASan unit test from a set of objects in ${ARGN}.
+macro(add_asan_test test_suite test_name)
+  message(STATUS "Link flags: ${ASAN_LINK_FLAGS}")
+  add_compiler_rt_test(${test_suite} ${test_name}
+                       OBJECTS ${ARGN}
+                       DEPS ${ASAN_RUNTIME_LIBRARIES}
+                       LINK_FLAGS ${ASAN_LINK_FLAGS})
+endmacro()
 
-set(ASAN_INST_TEST_OBJECTS)
+# Main AddressSanitizer unit tests.
+add_custom_target(AsanUnitTests)
+set_target_properties(AsanUnitTests PROPERTIES FOLDER "ASan unit tests")
+# ASan benchmarks (not actively used now).
+add_custom_target(AsanBenchmarks)
+set_target_properties(AsanBenchmarks PROPERTIES FOLDER "Asan benchmarks")
 
 # We only support building instrumented tests when we're not cross compiling
 # and targeting a unix-like system where we can predict viable compilation and
 # linking strategies.
 # We use a different approach to build these tests for Android. See below.
 if("${CMAKE_HOST_SYSTEM}" STREQUAL "${CMAKE_SYSTEM}" AND UNIX AND NOT ANDROID)
-
-  # This function is a custom routine to manage manually compiling source files
-  # for unit tests with the just-built Clang binary, using the ASan
-  # instrumentation, and linking them into a test executable.
-  function(add_asan_compile_command source extra_cflags)
-    set(output_obj "${source}.asan.o")
-    add_custom_command(
-      OUTPUT ${output_obj}
-      COMMAND clang
-              ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}
-              ${extra_cflags}
-              -c -o "${output_obj}"
-              ${CMAKE_CURRENT_SOURCE_DIR}/${source}
-      MAIN_DEPENDENCY ${source}
-      DEPENDS clang ${ASAN_RUNTIME_LIBRARIES} ${ASAN_BLACKLIST_FILE} ${ARGN}
-      )
-  endfunction()
-
-  add_asan_compile_command(asan_globals_test.cc "")
-  add_asan_compile_command(asan_test.cc "")
-  list(APPEND ASAN_INST_TEST_OBJECTS asan_globals_test.cc.asan.o
-                                     asan_test.cc.asan.o)
+  # Build gtest instrumented with ASan.
+  set(ASAN_INST_GTEST)
+  asan_compile(ASAN_INST_GTEST ${COMPILER_RT_GTEST_SOURCE}  
+               ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS})
+  # Instrumented tests.
+  set(ASAN_INST_TEST_OBJECTS)
+  asan_compile(ASAN_INST_TEST_OBJECTS asan_globals_test.cc
+               ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS})
+  asan_compile(ASAN_INST_TEST_OBJECTS asan_test.cc
+               ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS})
   if (APPLE)
-    add_asan_compile_command(asan_mac_test.mm "-ObjC")
-    list(APPEND ASAN_INST_TEST_OBJECTS asan_mac_test.mm.asan.o)
+    asan_compile(ASAN_INST_TEST_OBJECTS asan_mac_test.mm
+                 ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS} -ObjC)
   endif()
-
-  # Build benchmarks test instrumented with AddressSanitizer.
-  add_asan_compile_command(asan_benchmarks_test.cc "")
-  add_custom_target(AsanBenchmarks)
-  set_target_properties(AsanBenchmarks PROPERTIES FOLDER "Asan benchmarks")
-  add_asan_test(AsanBenchmarks AsanBenchmark asan_benchmarks_test.cc.asan.o)
+  # Uninstrumented tests.
+  set(ASAN_NOINST_TEST_OBJECTS)
+  asan_compile(ASAN_NOINST_TEST_OBJECTS asan_noinst_test.cc
+               ${ASAN_UNITTEST_COMMON_CFLAGS})
+  asan_compile(ASAN_NOINST_TEST_OBJECTS asan_test_main.cc
+               ${ASAN_UNITTEST_COMMON_CFLAGS})
+
+  # Link everything together.
+  add_asan_test(AsanUnitTests AsanTest ${ASAN_NOINST_TEST_OBJECTS}
+                ${ASAN_INST_TEST_OBJECTS} ${ASAN_INST_GTEST})
+
+  # Instrumented benchmarks.
+  set(ASAN_BENCHMARKS_OBJECTS)
+  asan_compile(ASAN_BENCHMARKS_OBJECTS asan_benchmarks_test.cc
+               ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS})
+  # Link benchmarks.
+  add_asan_test(AsanBenchmarks AsanBenchmark ${ASAN_BENCHMARKS_OBJECTS}
+                ${ASAN_INST_GTEST})
 endif()
 
-# Main AddressSanitizer unit tests.
-add_custom_target(AsanUnitTests)
-set_target_properties(AsanUnitTests PROPERTIES FOLDER "ASan unit tests")
-
 if(ANDROID)
+  # We assume that unit tests on Android are built in a build
+  # tree with fresh Clang as a host compiler.
+  set(ASAN_NOINST_TEST_SOURCES asan_noinst_test.cc asan_test_main.cc)
   set(ASAN_INST_TEST_SOURCES asan_globals_test.cc asan_test.cc)
-  add_library(asan_noinst_test OBJECT
-    ${ASAN_NOINST_TEST_SOURCES}
-    )
-  set_target_compile_flags(asan_noinst_test
-    ${ASAN_UNITTEST_COMMON_CFLAGS} ${ASAN_GTEST_INCLUDE_CFLAGS}
-    )
-  add_asan_test(AsanUnitTests AsanTest
-    ${ASAN_INST_TEST_SOURCES}
+  add_library(asan_noinst_test OBJECT ${ASAN_NOINST_TEST_SOURCES})
+  set_target_compile_flags(asan_noinst_test ${ASAN_UNITTEST_COMMON_CFLAGS})
+  add_library(asan_inst_test OBJECT
+              ${ASAN_INST_TEST_SOURCES} ${COMPILER_RT_GTEST_SOURCE})  
+  set_target_compile_flags(asan_inst_test ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS})
+  add_executable(AsanTest
     $<TARGET_OBJECTS:asan_noinst_test>
-    )
-  set_target_compile_flags(AsanTest
-    ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS} ${ASAN_GTEST_INCLUDE_CFLAGS}
-    )
-  set_target_link_flags(AsanTest
-    -pie
-    )
-else()
-  add_asan_test(AsanUnitTests AsanTest ${ASAN_NOINST_TEST_SOURCES}
-    ${ASAN_INST_TEST_OBJECTS})
+    $<TARGET_OBJECTS:asan_inst_test>
+  )
+  # Setup correct output directory and link flags.
+  get_unittest_directory(OUTPUT_DIR)
+  set_target_properties(AsanTest PROPERTIES
+    RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR})
+  set_target_link_flags(AsanTest ${ASAN_LINK_FLAGS})
+  add_dependencies(AsanUnitTests AsanTest)
 endif()