forked from OSchip/llvm-project
Introduce stats and stats_client libraries.
This is part of a new statistics gathering feature for the sanitizers. See clang/docs/SanitizerStats.rst for further info and docs. Differential Revision: http://reviews.llvm.org/D16176 llvm-svn: 257972
This commit is contained in:
parent
dc13453128
commit
5788e1259b
|
@ -15,6 +15,7 @@ if(COMPILER_RT_BUILD_SANITIZERS)
|
|||
|
||||
if(COMPILER_RT_HAS_SANITIZER_COMMON)
|
||||
add_subdirectory(sanitizer_common)
|
||||
add_subdirectory(stats)
|
||||
add_subdirectory(lsan)
|
||||
add_subdirectory(ubsan)
|
||||
endif()
|
||||
|
|
|
@ -522,6 +522,19 @@ class InternalMmapVectorNoCtor {
|
|||
void clear() { size_ = 0; }
|
||||
bool empty() const { return size() == 0; }
|
||||
|
||||
const T *begin() const {
|
||||
return data();
|
||||
}
|
||||
T *begin() {
|
||||
return data();
|
||||
}
|
||||
const T *end() const {
|
||||
return data() + size();
|
||||
}
|
||||
T *end() {
|
||||
return data() + size();
|
||||
}
|
||||
|
||||
private:
|
||||
void Resize(uptr new_capacity) {
|
||||
CHECK_GT(new_capacity, 0);
|
||||
|
|
|
@ -45,17 +45,44 @@ void CommonFlags::CopyFrom(const CommonFlags &other) {
|
|||
internal_memcpy(this, &other, sizeof(*this));
|
||||
}
|
||||
|
||||
// Copy the string from "s" to "out", replacing "%b" with the binary basename.
|
||||
static void SubstituteBinaryName(const char *s, char *out, uptr out_size) {
|
||||
// Copy the string from "s" to "out", making the following substitutions:
|
||||
// %b = binary basename
|
||||
// %p = pid
|
||||
void SubstituteForFlagValue(const char *s, char *out, uptr out_size) {
|
||||
char *out_end = out + out_size;
|
||||
while (*s && out < out_end - 1) {
|
||||
if (s[0] != '%' || s[1] != 'b') { *out++ = *s++; continue; }
|
||||
const char *base = GetProcessName();
|
||||
CHECK(base);
|
||||
while (*base && out < out_end - 1)
|
||||
*out++ = *base++;
|
||||
s += 2; // skip "%b"
|
||||
if (s[0] != '%') {
|
||||
*out++ = *s++;
|
||||
continue;
|
||||
}
|
||||
switch (s[1]) {
|
||||
case 'b': {
|
||||
const char *base = GetProcessName();
|
||||
CHECK(base);
|
||||
while (*base && out < out_end - 1)
|
||||
*out++ = *base++;
|
||||
s += 2; // skip "%b"
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
int pid = internal_getpid();
|
||||
char buf[32];
|
||||
char *buf_pos = buf + 32;
|
||||
do {
|
||||
*--buf_pos = (pid % 10) + '0';
|
||||
pid /= 10;
|
||||
} while (pid);
|
||||
while (buf_pos < buf + 32 && out < out_end - 1)
|
||||
*out++ = *buf_pos++;
|
||||
s += 2; // skip "%p"
|
||||
break;
|
||||
}
|
||||
default:
|
||||
*out++ = *s++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(out < out_end - 1);
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
|
@ -69,7 +96,7 @@ class FlagHandlerInclude : public FlagHandlerBase {
|
|||
bool Parse(const char *value) final {
|
||||
if (internal_strchr(value, '%')) {
|
||||
char *buf = (char *)MmapOrDie(kMaxPathLength, "FlagHandlerInclude");
|
||||
SubstituteBinaryName(value, buf, kMaxPathLength);
|
||||
SubstituteForFlagValue(value, buf, kMaxPathLength);
|
||||
bool res = parser_->ParseFile(buf, ignore_missing_);
|
||||
UnmapOrDie(buf, kMaxPathLength);
|
||||
return res;
|
||||
|
|
|
@ -46,6 +46,8 @@ inline void OverrideCommonFlags(const CommonFlags &cf) {
|
|||
common_flags_dont_use.CopyFrom(cf);
|
||||
}
|
||||
|
||||
void SubstituteForFlagValue(const char *s, char *out, uptr out_size);
|
||||
|
||||
class FlagParser;
|
||||
void RegisterCommonFlags(FlagParser *parser,
|
||||
CommonFlags *cf = &common_flags_dont_use);
|
||||
|
|
|
@ -107,10 +107,8 @@ typedef uptr operator_new_size_type;
|
|||
#else
|
||||
typedef u32 operator_new_size_type;
|
||||
#endif
|
||||
} // namespace __sanitizer
|
||||
|
||||
|
||||
using namespace __sanitizer; // NOLINT
|
||||
// ----------- ATTENTION -------------
|
||||
// This header should NOT include any other headers to avoid portability issues.
|
||||
|
||||
|
@ -188,14 +186,12 @@ typedef void* thread_return_t;
|
|||
typedef thread_return_t (THREAD_CALLING_CONV *thread_callback_t)(void* arg);
|
||||
|
||||
// NOTE: Functions below must be defined in each run-time.
|
||||
namespace __sanitizer {
|
||||
void NORETURN Die();
|
||||
|
||||
// FIXME: No, this shouldn't be in the sanitizer interface.
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void NORETURN CheckFailed(const char *file, int line, const char *cond,
|
||||
u64 v1, u64 v2);
|
||||
} // namespace __sanitizer
|
||||
|
||||
// Check macro
|
||||
#define RAW_CHECK_MSG(expr, msg) do { \
|
||||
|
@ -287,6 +283,9 @@ enum LinkerInitialized { LINKER_INITIALIZED = 0 };
|
|||
#if !defined(_MSC_VER) || defined(__clang__)
|
||||
# define GET_CALLER_PC() (uptr)__builtin_return_address(0)
|
||||
# define GET_CURRENT_FRAME() (uptr)__builtin_frame_address(0)
|
||||
inline void Trap() {
|
||||
__builtin_trap();
|
||||
}
|
||||
#else
|
||||
extern "C" void* _ReturnAddress(void);
|
||||
# pragma intrinsic(_ReturnAddress)
|
||||
|
@ -295,6 +294,12 @@ extern "C" void* _ReturnAddress(void);
|
|||
// FIXME: This macro is still used when printing error reports though it's not
|
||||
// clear if the BP value is needed in the ASan reports on Windows.
|
||||
# define GET_CURRENT_FRAME() (uptr)0xDEADBEEF
|
||||
|
||||
extern "C" void __ud2(void);
|
||||
# pragma intrinsic(__ud2)
|
||||
inline void Trap() {
|
||||
__ud2();
|
||||
}
|
||||
#endif
|
||||
|
||||
#define HANDLE_EINTR(res, f) \
|
||||
|
@ -313,4 +318,8 @@ extern "C" void* _ReturnAddress(void);
|
|||
(void)enable_fp; \
|
||||
} while (0)
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
using namespace __sanitizer; // NOLINT
|
||||
|
||||
#endif // SANITIZER_DEFS_H
|
||||
|
|
|
@ -113,6 +113,8 @@ class Symbolizer final {
|
|||
void AddHooks(StartSymbolizationHook start_hook,
|
||||
EndSymbolizationHook end_hook);
|
||||
|
||||
LoadedModule *FindModuleForAddress(uptr address);
|
||||
|
||||
private:
|
||||
// GetModuleNameAndOffsetForPC has to return a string to the caller.
|
||||
// Since the corresponding module might get unloaded later, we should create
|
||||
|
@ -139,7 +141,6 @@ class Symbolizer final {
|
|||
|
||||
bool FindModuleNameAndOffsetForAddress(uptr address, const char **module_name,
|
||||
uptr *module_offset);
|
||||
LoadedModule *FindModuleForAddress(uptr address);
|
||||
LoadedModule modules_[kMaxNumberOfModules];
|
||||
uptr n_modules_;
|
||||
// If stale, need to reload the modules before looking up addresses.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
include_directories(..)
|
||||
|
||||
add_custom_target(stats)
|
||||
|
||||
if(APPLE)
|
||||
set(STATS_LIB_FLAVOR SHARED)
|
||||
else()
|
||||
set(STATS_LIB_FLAVOR STATIC)
|
||||
endif()
|
||||
|
||||
add_compiler_rt_runtime(clang_rt.stats
|
||||
${STATS_LIB_FLAVOR}
|
||||
ARCHS ${SANITIZER_COMMON_SUPPORTED_ARCH}
|
||||
OS ${SANITIZER_COMMON_SUPPORTED_OS}
|
||||
SOURCES stats.cc
|
||||
OBJECT_LIBS RTSanitizerCommon
|
||||
RTSanitizerCommonLibc
|
||||
CFLAGS ${SANITIZER_COMMON_CFLAGS}
|
||||
PARENT_TARGET stats)
|
||||
|
||||
add_compiler_rt_runtime(clang_rt.stats_client
|
||||
STATIC
|
||||
ARCHS ${SANITIZER_COMMON_SUPPORTED_ARCH}
|
||||
OS ${SANITIZER_COMMON_SUPPORTED_OS}
|
||||
SOURCES stats_client.cc
|
||||
CFLAGS ${SANITIZER_COMMON_CFLAGS}
|
||||
PARENT_TARGET stats)
|
|
@ -0,0 +1,136 @@
|
|||
//===-- stats.cc ----------------------------------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Sanitizer statistics gathering. Manages statistics for a process and is
|
||||
// responsible for writing the report file.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "sanitizer_common/sanitizer_internal_defs.h"
|
||||
#if SANITIZER_POSIX
|
||||
#include "sanitizer_common/sanitizer_posix.h"
|
||||
#endif
|
||||
#include "sanitizer_common/sanitizer_symbolizer.h"
|
||||
#include "stats/stats.h"
|
||||
#if SANITIZER_POSIX
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
using namespace __sanitizer;
|
||||
|
||||
namespace {
|
||||
|
||||
InternalMmapVectorNoCtor<StatModule **> modules;
|
||||
StaticSpinMutex modules_mutex;
|
||||
|
||||
fd_t stats_fd;
|
||||
|
||||
void WriteLE(fd_t fd, uptr val) {
|
||||
char chars[sizeof(uptr)];
|
||||
for (unsigned i = 0; i != sizeof(uptr); ++i) {
|
||||
chars[i] = val >> (i * 8);
|
||||
}
|
||||
WriteToFile(fd, chars, sizeof(uptr));
|
||||
}
|
||||
|
||||
void OpenStatsFile(const char *path_env) {
|
||||
InternalScopedBuffer<char> path(kMaxPathLength);
|
||||
SubstituteForFlagValue(path_env, path.data(), kMaxPathLength);
|
||||
|
||||
error_t err;
|
||||
stats_fd = OpenFile(path.data(), WrOnly, &err);
|
||||
if (stats_fd == kInvalidFd) {
|
||||
Report("stats: failed to open %s for writing (reason: %d)\n", path.data(),
|
||||
err);
|
||||
return;
|
||||
}
|
||||
char sizeof_uptr = sizeof(uptr);
|
||||
WriteToFile(stats_fd, &sizeof_uptr, 1);
|
||||
}
|
||||
|
||||
void WriteModuleReport(StatModule **smodp) {
|
||||
CHECK(smodp);
|
||||
const char *path_env = GetEnv("SANITIZER_STATS_PATH");
|
||||
if (!path_env || stats_fd == kInvalidFd)
|
||||
return;
|
||||
if (!stats_fd)
|
||||
OpenStatsFile(path_env);
|
||||
LoadedModule *mod = Symbolizer::GetOrInit()->FindModuleForAddress(
|
||||
reinterpret_cast<uptr>(smodp));
|
||||
WriteToFile(stats_fd, mod->full_name(),
|
||||
internal_strlen(mod->full_name()) + 1);
|
||||
for (StatModule *smod = *smodp; smod; smod = smod->next) {
|
||||
for (u32 i = 0; i != smod->size; ++i) {
|
||||
StatInfo *s = &smod->infos[i];
|
||||
if (!s->addr)
|
||||
continue;
|
||||
WriteLE(stats_fd, s->addr - mod->base_address());
|
||||
WriteLE(stats_fd, s->data);
|
||||
}
|
||||
}
|
||||
WriteLE(stats_fd, 0);
|
||||
WriteLE(stats_fd, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
unsigned __sanitizer_stats_register(StatModule **mod) {
|
||||
SpinMutexLock l(&modules_mutex);
|
||||
modules.push_back(mod);
|
||||
return modules.size() - 1;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __sanitizer_stats_unregister(unsigned index) {
|
||||
SpinMutexLock l(&modules_mutex);
|
||||
WriteModuleReport(modules[index]);
|
||||
modules[index] = 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void WriteFullReport() {
|
||||
SpinMutexLock l(&modules_mutex);
|
||||
for (StatModule **mod : modules) {
|
||||
if (!mod)
|
||||
continue;
|
||||
WriteModuleReport(mod);
|
||||
}
|
||||
if (stats_fd != 0 && stats_fd != kInvalidFd) {
|
||||
CloseFile(stats_fd);
|
||||
stats_fd = kInvalidFd;
|
||||
}
|
||||
}
|
||||
|
||||
#if SANITIZER_POSIX
|
||||
void USR2Handler(int sig) {
|
||||
WriteFullReport();
|
||||
}
|
||||
#endif
|
||||
|
||||
struct WriteReportOnExitOrSignal {
|
||||
WriteReportOnExitOrSignal() {
|
||||
#if SANITIZER_POSIX
|
||||
struct sigaction sigact;
|
||||
internal_memset(&sigact, 0, sizeof(sigact));
|
||||
sigact.sa_handler = USR2Handler;
|
||||
internal_sigaction(SIGUSR2, &sigact, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
~WriteReportOnExitOrSignal() {
|
||||
WriteFullReport();
|
||||
}
|
||||
} wr;
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,43 @@
|
|||
//===-- stats.h -------------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Data definitions for sanitizer statistics gathering.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SANITIZER_STATS_STATS_H
|
||||
#define SANITIZER_STATS_STATS_H
|
||||
|
||||
#include "sanitizer_common/sanitizer_internal_defs.h"
|
||||
|
||||
namespace __sanitizer {
|
||||
|
||||
// Number of bits in data that are used for the sanitizer kind. Needs to match
|
||||
// llvm::kSanitizerStatKindBits in
|
||||
// llvm/include/llvm/Transforms/Utils/SanitizerStats.h
|
||||
enum { kKindBits = 3 };
|
||||
|
||||
struct StatInfo {
|
||||
uptr addr;
|
||||
uptr data;
|
||||
};
|
||||
|
||||
struct StatModule {
|
||||
StatModule *next;
|
||||
u32 size;
|
||||
StatInfo infos[1];
|
||||
};
|
||||
|
||||
inline uptr CountFromData(uptr data) {
|
||||
return data & ((1ull << (sizeof(uptr) * 8 - kKindBits)) - 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
//===-- stats_client.cc ---------------------------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Sanitizer statistics gathering. Manages statistics for a module (executable
|
||||
// or DSO) and registers statistics with the process.
|
||||
//
|
||||
// This is linked into each individual modle and cannot directly use functions
|
||||
// declared in sanitizer_common.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sanitizer_common/sanitizer_internal_defs.h"
|
||||
#include "stats/stats.h"
|
||||
|
||||
using namespace __sanitizer;
|
||||
|
||||
namespace {
|
||||
|
||||
void *LookupSymbolFromMain(const char *name) {
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void *>(GetProcAddress(GetModuleHandle(0), name));
|
||||
#else
|
||||
return dlsym(RTLD_DEFAULT, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
StatModule *list;
|
||||
|
||||
struct RegisterSanStats {
|
||||
unsigned module_id;
|
||||
|
||||
RegisterSanStats() {
|
||||
typedef unsigned (*reg_func_t)(StatModule **);
|
||||
reg_func_t reg_func = reinterpret_cast<reg_func_t>(
|
||||
LookupSymbolFromMain("__sanitizer_stats_register"));
|
||||
if (reg_func)
|
||||
module_id = reg_func(&list);
|
||||
}
|
||||
|
||||
~RegisterSanStats() {
|
||||
typedef void (*unreg_func_t)(unsigned);
|
||||
unreg_func_t unreg_func = reinterpret_cast<unreg_func_t>(
|
||||
LookupSymbolFromMain("__sanitizer_stats_unregister"));
|
||||
if (unreg_func)
|
||||
unreg_func(module_id);
|
||||
}
|
||||
} reg;
|
||||
|
||||
}
|
||||
|
||||
extern "C" void __sanitizer_stat_init(StatModule *mod) {
|
||||
mod->next = list;
|
||||
list = mod;
|
||||
}
|
||||
|
||||
extern "C" void __sanitizer_stat_report(StatInfo *s) {
|
||||
s->addr = GET_CALLER_PC();
|
||||
#if defined(_WIN64) && !defined(__clang__)
|
||||
uptr old_data = InterlockedIncrement64(reinterpret_cast<LONG64 *>(&s->data));
|
||||
#elif defined(_WIN32) && !defined(__clang__)
|
||||
uptr old_data = InterlockedIncrement(&s->data);
|
||||
#else
|
||||
uptr old_data = __sync_fetch_and_add(&s->data, 1);
|
||||
#endif
|
||||
|
||||
// Overflow check.
|
||||
if (CountFromData(old_data + 1) == 0)
|
||||
Trap();
|
||||
}
|
|
@ -8,6 +8,8 @@ if(NOT COMPILER_RT_STANDALONE_BUILD)
|
|||
list(APPEND CFI_TEST_DEPS
|
||||
opt
|
||||
ubsan
|
||||
stats
|
||||
sanstats
|
||||
)
|
||||
if(COMPILER_RT_HAS_CFI)
|
||||
list(APPEND CFI_TEST_DEPS cfi)
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// RUN: %clangxx_cfi_dso -DSHARED_LIB -fPIC -g -fsanitize-stats -shared -o %t.so %s
|
||||
// RUN: %clangxx_cfi_dso -g -fsanitize-stats -o %t %s %t.so
|
||||
// RUN: env SANITIZER_STATS_PATH=%t.stats %t
|
||||
// RUN: sanstats %t.stats | FileCheck %s
|
||||
|
||||
struct ABase {};
|
||||
|
||||
struct A : ABase {
|
||||
virtual void vf() {}
|
||||
void nvf() {}
|
||||
};
|
||||
|
||||
extern "C" void vcall(A *a);
|
||||
extern "C" void nvcall(A *a);
|
||||
|
||||
#ifdef SHARED_LIB
|
||||
|
||||
extern "C" __attribute__((noinline)) void vcall(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] vcall cfi-vcall 37
|
||||
a->vf();
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) void nvcall(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] nvcall cfi-nvcall 51
|
||||
a->nvf();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
extern "C" __attribute__((noinline)) A *dcast(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] dcast cfi-derived-cast 24
|
||||
return (A *)(ABase *)a;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) A *ucast(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] ucast cfi-unrelated-cast 81
|
||||
return (A *)(char *)a;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) void unreachable(A *a) {
|
||||
// CHECK-NOT: unreachable
|
||||
a->vf();
|
||||
}
|
||||
|
||||
int main() {
|
||||
A a;
|
||||
for (unsigned i = 0; i != 37; ++i)
|
||||
vcall(&a);
|
||||
for (unsigned i = 0; i != 51; ++i)
|
||||
nvcall(&a);
|
||||
for (unsigned i = 0; i != 24; ++i)
|
||||
dcast(&a);
|
||||
for (unsigned i = 0; i != 81; ++i)
|
||||
ucast(&a);
|
||||
for (unsigned i = 0; i != 0; ++i)
|
||||
unreachable(&a);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,49 @@
|
|||
// RUN: %clangxx_cfi -g -fsanitize-stats -o %t %s
|
||||
// RUN: env SANITIZER_STATS_PATH=%t.stats %t
|
||||
// RUN: sanstats %t.stats | FileCheck %s
|
||||
|
||||
struct ABase {};
|
||||
|
||||
struct A : ABase {
|
||||
virtual void vf() {}
|
||||
void nvf() {}
|
||||
};
|
||||
|
||||
extern "C" __attribute__((noinline)) void vcall(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] vcall cfi-vcall 37
|
||||
a->vf();
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) void nvcall(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] nvcall cfi-nvcall 51
|
||||
a->nvf();
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) A *dcast(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] dcast cfi-derived-cast 24
|
||||
return (A *)(ABase *)a;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) A *ucast(A *a) {
|
||||
// CHECK: stats.cpp:[[@LINE+1]] ucast cfi-unrelated-cast 81
|
||||
return (A *)(char *)a;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((noinline)) void unreachable(A *a) {
|
||||
// CHECK-NOT: unreachable
|
||||
a->vf();
|
||||
}
|
||||
|
||||
int main() {
|
||||
A a;
|
||||
for (unsigned i = 0; i != 37; ++i)
|
||||
vcall(&a);
|
||||
for (unsigned i = 0; i != 51; ++i)
|
||||
nvcall(&a);
|
||||
for (unsigned i = 0; i != 24; ++i)
|
||||
dcast(&a);
|
||||
for (unsigned i = 0; i != 81; ++i)
|
||||
ucast(&a);
|
||||
for (unsigned i = 0; i != 0; ++i)
|
||||
unreachable(&a);
|
||||
}
|
Loading…
Reference in New Issue