blob: 08628fb8999d2a6b58ba0b06478b136977422c91 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 UTIL_UBSAN_H_
#define UTIL_UBSAN_H_
// Utilities mimicking parts of the standard prone to accidentally using in a way causeing
// undefined behavior.
#include <cstring>
#include <type_traits>
#include "common/logging.h"
class Ubsan {
public:
static void* MemSet(void* s, int c, size_t n) {
if (s == nullptr) {
DCHECK_EQ(n, 0);
return s;
}
return std::memset(s, c, n);
}
static void* MemCpy(void* dest, const void* src, size_t n) {
if (dest == nullptr || src == nullptr) {
DCHECK_EQ(n, 0);
return dest;
}
return std::memcpy(dest, src, n);
}
static int MemCmp(const void *s1, const void *s2, size_t n) {
if (s1 == nullptr || s2 == nullptr) {
DCHECK_EQ(n, 0);
return 0;
}
return std::memcmp(s1, s2, n);
}
// Convert a potential enum value (that may be out of range) into its underlying integer
// implementation. This is required because, according to the standard, enum values that
// are out of range induce undefined behavior. For instance,
//
// enum A {B = 0, C = 5};
// int d = 6;
// A e;
// memcpy(&e, &d, sizeof(e));
// if (e == B)
// ; // undefined behavior: load of value 6, which is not a valid value for type 'A'
// if (EnumToInt(&e) == B)
// ; // OK: the type of EnumToInt(&e) is int, and B is converted to int for the
// // comparison
//
// EnumToInt() is a worse alternative to not treating a pointer to arbitrary memory as a
// pointer to an enum. To put it another way, the first block below is a better way to
// handle possibly-out-of-range enum values:
//
// extern char * v;
// enum A { B = 0, C = 45 };
// A x;
// if (good_way) {
// std::underlying_type_t<T> i;
// std::memcpy(&i, v, sizeof(i));
// if (B <= i && i <= C) x = i;
// } else {
// A * y = reinterpret_cast<A *>(v);
// int i = EnumToInt(y);
// if (B <= i && i <= C) x = *y;
// }
//
// The second block is worse because y is masquerading as a legitimate pointer to an A
// and could get dereferenced illegally as the code evolves. Unfortunately,
// deserialization methods don't always make the better way an option - sometimes the
// possibly invalid pointer to A (like y) is created externally.
template<typename T>
static auto EnumToInt(const T * e) {
std::underlying_type_t<T> i;
static_assert(sizeof(i) == sizeof(*e), "enum underlying type is the wrong size");
// We have to memcpy, rather than directly assigning i = *e, because dereferencing e
// creates undefined behavior.
memcpy(&i, e, sizeof(i));
return i;
}
};
#endif // UTIL_UBSAN_H_