Program Listing for File gpa-checkret.h
↰ Return to documentation for file (include\instrumentation\gpa-checkret.h
)
/******************************************************************************
Copyright (c) 2023 Intel Corporation.
This software and the related documents are Intel copyrighted materials,
and your use of them is governed by the express license under which they
were provided to you ("License"). Unless the License provides otherwise,
you may not use, modify, copy, publish, distribute, disclose or transmit
this software or the related documents without Intel's prior written
permission.
This software and the related documents are provided as is, with no express
or implied warranties, other than those that are expressly stated in the
License.
******************************************************************************/
/*
GPA_CHECK_RET is the error handling technique, that is very simple, but covers big set of standard C++ code issues.
Usage example:
int function1(void* ptr)
{
GPA_CHECK_RET(ptr, -1); //this works as: if (!ptr) {<REPORT>; return -1;}
string res = function2(ptr);
GPA_CHECK_RET(res.size(), -1);
auto tr = res.transform();
GPA_CHECK_RET(tr, -1);
if (res[0] == '\n') return 13;
return 0;
}
The code covered with checkrets has clear distinction between product logic and error handling code.
DEBUGGING:
Every unsuccessful GPA_CHECK_RET is logged as error.
DEFAULT FLOW: Before framework's logger module is initialized (see gpa:logger::internal::CCheckretReporter), issues are gathered to a buffer file, that is dumped to the logger once it is loaded. If no logger was loaded, at exit file is dumped to stdout.
FLAVOURS: you can set the "GPA_CHECK_RET" environment variable to: stderr, stdout, assert, break, or mix them
set GPA_CHECK_RET = stderr|assert # will break on every failed CHECKRET with a MessageBox after writing it to stderr
set GPA_CHECK_RET = break # will break debugger on every failed CHECKRET
With good distribution of GPA_CHECK_RET in source code, just the log from client is completely enough to fix his problem without trying to repro.
CODE COVERAGE:
The GPA_CHECK_RET can be safely replaced by empty macro in the code that passes without errors and used as significant code-coverage booster (error handling branches in usual code spoil code-coverage at least by 50%)
PERFORMANCE:
The cost is "single if instruction" with prediction toward "no issue", so even the prefetch is tuned up toward performance of non failing code.
GPA_CHECK_RET can have perfomance implication only in case of "if (condition)" being too expencive in a highly performance critical bottleneck.
Usually this is the type of code when you ponder to use assembly to speed up. In the rest of the cases you will never see a GPA_CHECK_RET being a performance threat.
CUMMULATIVE VALUE:
Logger adds stacks on errors
Seismo gathers multiple test logs data into issue frequency graph
FUTURE FEATURES:
GPA_CHECK_RET(one.size() < two.length(), -1); // this will log only "one.size() < two.length()" text
GPA_CHECK_RET($(one.size()) < $(two.length()), -1); // this will log "one.size():value < two.length():value"
*/
#pragma once
#ifdef _WIN32
#include "windows.h"
#include "debugapi.h"
#else
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#endif
#include "gpa-singleton.h"
#include "gpa-secure.h"
#include "gpa-formatter.h"
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <filesystem>
#include <vector>
#ifdef _MSC_VER
#define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
namespace gpa {
namespace checkret {
struct IReporter
{
virtual void Report(const char* message) = 0;
};
class CDefaultReporter : public IReporter
{
bool mWriteToTemp = false;
public:
CDefaultReporter()
: mWriteToTemp(true)
{
}
~CDefaultReporter()
{
if (!mWriteToTemp) {
return;
}
mWriteToTemp = false;
if (const char* szPath = (char*)gpa::ProcessWideSingletonRegistry::Get("GPA_REPORTER_FILE", nullptr)) {
std::ifstream file(szPath);
std::cout << file.rdbuf();
}
}
void SetWriteToTemp(bool bWriteToTemp) { mWriteToTemp = bWriteToTemp; }
void DebugString(const char* message)
{
#ifdef _WIN32
static bool bIsDebuggerPresent = ::IsDebuggerPresent();
if (bIsDebuggerPresent) {
OutputDebugStringA(message);
}
#else
openlog("GPA", LOG_PID | LOG_CONS, LOG_USER);
syslog(LOG_DEBUG, "%s", message);
closelog();
#endif
}
static void WriteToTemp(const char* message)
{
static char path[1024] = {};
const char* szPath = (char*)gpa::ProcessWideSingletonRegistry::Get("GPA_REPORTER_FILE", path);
if (!szPath[0]) {
try {
std::filesystem::path tmp = std::filesystem::temp_directory_path();
tmp /= "GPA_REPORTER_FILE.txt";
if (tmp.string().length() >= sizeof(path)) {
throw std::filesystem::filesystem_error("buffer overflow. sizeof path exceeds buffer size", std::error_code());
}
strcpy(path, tmp.string().c_str());
gpa::ProcessWideSingletonRegistry::Set("GPA_REPORTER_FILE", path);
std::filesystem::remove(tmp);
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Error retrieving temporary directory: " << e.what() << std::endl;
return;
}
}
FILE* file = fopen(szPath, "a");
if (!file) {
return;
}
// Use fprintf to append text to the file
fprintf(file, "%s\n", message);
// Close the file
fclose(file);
}
virtual void Report(const char* message)
{
DebugString(message);
if (mWriteToTemp) {
WriteToTemp(message);
}
const char* env = std::getenv("GPA_CHECK_RET"); // should be gpa::system::Environment().GetPublic(gpa::system::eGPA_CHECK_RET), but that would create circular dependency
if (!env) {
return;
}
if (strstr(env, "stderr")) {
std::cerr << message;
}
if (strstr(env, "stdout")) {
std::cout << message;
}
if (strstr(env, "assert")) {
#ifdef _WIN32
::MessageBoxA(nullptr, message, "GPA_CHECK_RET", MB_OK);
#else
std::string msg = message;
std::replace(msg.begin(), msg.end(), '"', '\'');
msg = std::string("xmessage -center \"") + msg + "\"";
int res = ::system(msg.c_str()); //FIXME: fallback on zenity
(void)res;
#endif
}
#ifdef _WIN32
if (strstr(env, "break") && ::IsDebuggerPresent()) {
__debugbreak();
}
#else
if (strstr(env, "break")) {
__asm__ __volatile__("int $3\n\t");
}
#endif
}
static IReporter& DefaultReporter()
{
static CDefaultReporter reporter;
return reporter;
}
};
//print like (fmt, args...)
#define GPA_REPORT(...) gpa::ProcessWideSingletonRegistry::GetOrCreate<gpa::checkret::CDefaultReporter, gpa::checkret::IReporter>("Reporter").Report($(__VA_ARGS__).c_str())
#define GPA_IN_VOID_FUNCTION() ((std::string_view(__PRETTY_FUNCTION__).substr(0, 11) == "const void " || std::string_view(__PRETTY_FUNCTION__).substr(0, 5) == "void ") && std::string_view(__PRETTY_FUNCTION__).substr(0, 6) != "void *")
#define GPA_IN_DESTRUCTOR() (std::string_view(__PRETTY_FUNCTION__).find("::~") != std::string_view::npos)
#ifdef _WIN32
#define GPA_IN_CONSTRUCTOR() (std::string_view(__FUNCDNAME__).substr(0, 3) == "??0")
#define GPA_NO_RET_VAL() (GPA_IN_VOID_FUNCTION() || GPA_IN_DESTRUCTOR() || GPA_IN_CONSTRUCTOR())
#else
static constexpr std::string_view concatenate(const std::string_view parts[], size_t count, char* buffer)
{
size_t index = 0;
for (size_t i = 0; i < count; ++i) {
for (char c : parts[i]) {
buffer[index++] = c;
}
}
buffer[index] = '\0';
return std::string_view(buffer, index);
}
static constexpr bool is_constructor(std::string_view pretty_function)
{
constexpr size_t buffer_size = 256;
char buffer[buffer_size] = {};
// Find the first occurrence of "::"
auto pos1 = pretty_function.find("::");
if (pos1 == std::string_view::npos) {
return false;
}
// Find the second occurrence of "::"
auto pos2 = pretty_function.find("::", pos1 + 2);
if (pos2 == std::string_view::npos) {
return false;
}
// Extract the class name
auto class_name = pretty_function.substr(0, pos2);
auto constructor_marker = pretty_function.substr(pos1 + 2, pos2 - pos1 - 2); // Extract the constructor part (after the last "::")
// Construct the concatenated search pattern
std::string_view search_pattern[] = {class_name, "::", constructor_marker};
auto concatenated_pattern = concatenate(search_pattern, 3, buffer);
// Check if the function name matches the class name (constructor)
return pretty_function.find(concatenated_pattern) != std::string_view::npos;
}
#define GPA_NO_RET_VAL() (GPA_IN_VOID_FUNCTION() || GPA_IN_DESTRUCTOR() || gpa::checkret::is_constructor(__PRETTY_FUNCTION__))
#endif
//to return from a function no matter if it is void or needs a value
#define GPA_EXIT_FUNCTION() \
if constexpr (GPA_NO_RET_VAL()) { \
return; \
} else { \
return {}; \
}
//allows to replace the return with break/continue...
#define GPA_CHECK_DO(cond, todo) \
{ \
if (cond) { \
} else { \
GPA_REPORT("%s(%d): warning CHECK_RET: %s | \t %s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, #cond); \
todo; \
} \
}
#define GPA_CONCAT2(x, y) x##y
#define GPA_CONCAT(x, y) GPA_CONCAT2(x, y)
#define GPA_EXPAND(x) x
#define GPA_NARG(...) GPA_EXPAND(GPA_NARG1(__VA_ARGS__, GPA_RSEQN()))
#define GPA_NARG1(...) GPA_EXPAND(GPA_ARGSN(__VA_ARGS__))
#define GPA_ARGSN(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define GPA_RSEQN() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define GPA_GET_MACRO(func, ...) \
GPA_CONCAT(func, GPA_EXPAND(GPA_NARG(__VA_ARGS__))) \
(__VA_ARGS__)
//Syntax sugar for overload by arg count: GPA_CHECK_RET(one_arg) is consider returning from void function
#define GPA_CHECK_RET(...) GPA_GET_MACRO(GPA_CHECK_RET, __VA_ARGS__)
#define GPA_CHECK_RET2(cond, ret) GPA_CHECK_DO(cond, return ret)
#define GPA_CHECK_RET1(cond) GPA_CHECK_DO(cond, return )
//Do the usual but don't return
#define GPA_CHECK_ONLY(cond) GPA_CHECK_DO(cond, ;)
struct Dummy
{
template<typename T>
operator T&()
{
static T dummy;
return dummy;
}
template<typename T>
operator const T&() const
{
static T dummy;
return dummy;
}
};
template<typename T>
struct DummyT
{
operator T&()
{
static T dummy;
return dummy;
}
operator const T&() const
{
static T dummy;
return dummy;
}
};
//when you can't avoid returning something: GPA_CHECK_RET(itr, GPA_DUMMY);
#define GPA_DUMMY gpa::checkret::Dummy()
#define GPA_DUMMY_T(the_type) (the_type) gpa::checkret::DummyT<the_type>()
} // namespace checkret
} // namespace gpa