blob: f83f6e150d265a99b40244e2057ca091116219e3 [file] [log] [blame]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SRC_PROTOVM_ERROR_HANDLING_H_
#define SRC_PROTOVM_ERROR_HANDLING_H_
#include <cstdarg>
#include <cstdint>
#include <memory>
#include <new>
#include <string>
#include <type_traits>
#include <vector>
#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
#if defined(__GNUC__) || defined(__clang__)
// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
#endif
namespace perfetto {
namespace protovm {
using Stacktrace = std::vector<std::string>;
void LogStacktraceMessage(Stacktrace& stacktrace,
const char* file_name,
int file_line,
const char* fmt,
...) PERFETTO_PRINTF_FORMAT(4, 5);
// overload for calls without message args
void LogStacktraceMessage(Stacktrace& stacktrace,
const char* file_name,
int file_line);
#define PROTOVM_ABORT(fmt, ...) \
do { \
auto status_abort = StatusOr<void>::Abort(); \
LogStacktraceMessage(status_abort.stacktrace(), \
::perfetto::base::Basename(__FILE__), __LINE__, fmt, \
##__VA_ARGS__); \
return status_abort; \
} while (0)
#define PROTOVM_RETURN(s, ...) \
do { \
if (s.IsAbort()) { \
LogStacktraceMessage(s.stacktrace(), \
::perfetto::base::Basename(__FILE__), __LINE__, \
##__VA_ARGS__); \
} \
return s; \
} while (0)
#define PROTOVM_RETURN_IF_NOT_OK(s, ...) \
do { \
if (s.IsAbort()) { \
LogStacktraceMessage(s.stacktrace(), \
::perfetto::base::Basename(__FILE__), __LINE__, \
##__VA_ARGS__); \
return s; \
} \
if (!s.IsOk()) { \
return s; \
} \
} while (0)
enum class Status : uint8_t {
kOk,
kError,
// It causes an abort of the whole VM's program. Typically indicates a
// fundamental issue with the program and requires developer's intervention.
kAbort,
};
namespace internal {
template <class T>
constexpr bool is_empty_class_v = std::is_class_v<T> && std::is_empty_v<T>;
template <class T, class Arg, class... Args>
static constexpr bool is_copy_or_move_ctor_arg_v =
sizeof...(Args) == 0 && std::is_same_v<T, std::decay_t<Arg>>;
template <class T>
inline constexpr std::size_t sizeof_v = sizeof(T);
template <>
inline constexpr std::size_t sizeof_v<void> = 0;
template <class T>
inline constexpr std::size_t alignof_v = alignof(T);
template <>
inline constexpr std::size_t alignof_v<void> = 0;
} // namespace internal
template <class T>
class StatusOr {
public:
// Factory methods to create empty instances (without value)
static StatusOr Ok() { return StatusOr{Status::kOk}; }
static StatusOr Error() { return StatusOr{Status::kError}; }
static StatusOr Abort() { return StatusOr{Status::kAbort}; }
StatusOr(const StatusOr&) = delete;
StatusOr(StatusOr&& other) : status_{other.status_} {
if (IsOk()) {
if constexpr (!std::is_same_v<T, void>) {
new (storage_) T(std::move(other.value()));
}
} else if (IsAbort()) {
new (storage_) std::unique_ptr<Stacktrace>(
std::move(other.GetStacktraceUniquePtr()));
}
}
// Intentionally implicit to allow idiomatic usage (return plain value to be
// implicitly converted to StatusOr)
template <
class... Args,
// sfinae to avoid clashing with copy/move constructors
std::enable_if_t<!internal::is_copy_or_move_ctor_arg_v<StatusOr, Args...>,
int> = 0>
StatusOr(Args&&... args) : status_{Status::kOk} {
if constexpr (!std::is_same_v<T, void>) {
new (storage_) T(std::forward<Args>(args)...);
}
}
// Intentionally implicit to allow idiomatic usage (e.g. return StatusOr<A> to
// be implicitely converted to StatusOr<B>).
// When status is ok, the conversion is only allowed if B = void, otherwise it
// would require a conversion from A to non-void B.
template <class U, std::enable_if_t<!std::is_same_v<U, T>, int> = 0>
StatusOr(StatusOr<U>&& other) : status_{other.status_} {
if (IsOk()) {
PERFETTO_DCHECK((std::is_same_v<T, void>));
} else if (IsAbort()) {
new (storage_) std::unique_ptr<Stacktrace>(
std::move(other.GetStacktraceUniquePtr()));
}
}
~StatusOr() {
if (IsOk()) {
if constexpr (!std::is_same_v<T, void>) {
value().~T();
}
}
if (IsAbort()) {
GetStacktraceUniquePtr().~unique_ptr();
}
}
bool IsOk() const { return status_ == Status::kOk; }
bool IsError() const { return status_ == Status::kError; }
bool IsAbort() const { return status_ == Status::kAbort; }
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, U&> value() {
PERFETTO_DCHECK(IsOk());
return *std::launder(reinterpret_cast<T*>(storage_));
}
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, const U&> value() const {
return const_cast<StatusOr*>(this)->value();
}
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, U&> operator*() {
return value();
}
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, const U&> operator*() const {
return value();
}
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, U*> operator->() {
return &value();
}
template <class U = T>
std::enable_if_t<!std::is_same_v<U, void>, const U*> operator->() const {
return &value();
}
Stacktrace& stacktrace() {
PERFETTO_DCHECK(IsAbort());
return **std::launder(
reinterpret_cast<std::unique_ptr<Stacktrace>*>(storage_));
}
private:
template <class U>
friend class StatusOr;
// Intentionally private to encourage the use of factory methods, instead of
// plain status values. E.g. StatusOr::Abort() instead of Status::kAbort.
// Plain status value don't include stacktrace information in case of an
// abort.
explicit StatusOr(Status status) : status_{status} {
PERFETTO_DCHECK((std::is_same_v<T, void>) || !IsOk());
if (IsAbort()) {
new (storage_) std::unique_ptr<Stacktrace>{new Stacktrace{}};
}
}
std::unique_ptr<Stacktrace>& GetStacktraceUniquePtr() {
PERFETTO_DCHECK(IsAbort());
return *std::launder(
reinterpret_cast<std::unique_ptr<Stacktrace>*>(storage_));
}
Status status_;
static constexpr size_t kStorageAlign =
std::max(internal::alignof_v<T>, alignof(std::unique_ptr<Stacktrace>));
static constexpr size_t kStorageSize =
std::max(internal::sizeof_v<T>, sizeof(std::unique_ptr<Stacktrace>));
alignas(kStorageAlign) unsigned char storage_[kStorageSize];
};
} // namespace protovm
} // namespace perfetto
#endif // SRC_PROTOVM_ERROR_HANDLING_H_