Files
ljkiwi/ckiwi/ckiwi.cpp
John K. Luebs e43272487f Guard against most egregious mistakes in calling the library
LuaJIT FFI is not inherently memory safe and there is no way
to completely guard against the caller doing something that
will trample over memory, but we can get pretty close. Biggest
issue is that an empty table will stand-in for a ref struct with
a null ref. So check for that in all the calls. In the calls that
raise errors we now have a specific error for it. In the other
functions the "nil" object is handled quietly but without a nullptr
dereference and hopefully no UB.
2024-02-13 16:58:59 -06:00

400 lines
10 KiB
C++

#include "ckiwi.h"
#include <kiwi/kiwi.h>
#include <cstdlib>
#include <cstring>
using namespace kiwi;
namespace {
template<typename T, typename R, typename... Args>
inline R to_cref(Args&&... args) {
static_assert(
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
"to_cref: R too small for T"
);
static_assert(alignof(R) >= alignof(T), "to_cref: R alignment too small for T");
R cref;
new (&cref) T(std::forward<Args>(args)...);
return cref;
}
template<typename... Args>
inline decltype(auto) to_var_cref(Args&&... args) {
return to_cref<Variable, KiwiVarRef>(std::forward<Args>(args)...);
}
template<typename... Args>
inline decltype(auto) to_constraint_cref(Args&&... args) {
return to_cref<Constraint, KiwiConstraintRef>(std::forward<Args>(args)...);
}
template<class T, class R>
class SharedRef {
private:
R& cref_;
public:
explicit SharedRef<T, R>(R& cref) : cref_(cref) {}
static_assert(
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
"SharedRef<T,CS> CS too small for T"
);
void destroy() {
if (cref_) {
ptr()->~T();
cref_ = nullptr;
}
}
T* ptr() {
T* p;
void* s = &cref_;
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
return p;
}
const T* const_ptr() const {
const T* p;
const void* s = &cref_;
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
return p;
}
const T& cref() const {
return *const_ptr();
}
T* operator&() const {
return ptr();
}
T* operator->() {
return ptr();
}
const T* operator->() const {
return const_ptr();
}
operator const T&() const {
return cref();
}
explicit operator bool() const {
return cref_;
}
};
using ConstraintRef = SharedRef<Constraint, KiwiConstraintRef>;
using VariableRef = SharedRef<Variable, KiwiVarRef>;
using ConstVariableRef = const SharedRef<const Variable, const KiwiVarRef>;
KiwiErrPtr new_error(KiwiErrPtr base, const std::exception& ex) {
if (!std::strcmp(ex.what(), base->message))
return base;
const auto msg_n = std::strlen(ex.what()) + 1;
auto* mem = static_cast<char*>(std::malloc(sizeof(KiwiErr) + msg_n));
if (!mem) {
return base;
}
const auto* err = new (mem) KiwiErr {base->kind, mem + sizeof(KiwiErr), true};
std::memcpy(const_cast<char*>(err->message), ex.what(), msg_n);
return err;
}
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
KiwiErrUnknown,
"An unhandled C++ exception occurred."};
static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
KiwiErrNullObject,
"null object passed as argument #0 (self)"};
static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
KiwiErrNullObject,
"null object passed as argument #1"};
template<typename F>
inline KiwiErrPtr wrap_err(F&& f) {
try {
f();
} catch (const UnsatisfiableConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrUnsatisfiableConstraint,
"The constraint cannot be satisfied."};
return &err;
} catch (const UnknownConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrUnknownConstraint,
"The constraint has not been added to the solver."};
return &err;
} catch (const DuplicateConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrDuplicateConstraint,
"The constraint has already been added to the solver."};
return &err;
} catch (const UnknownEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrUnknownEditVariable,
"The edit variable has not been added to the solver."};
return &err;
} catch (const DuplicateEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrDuplicateEditVariable,
"The edit variable has already been added to the solver."};
return &err;
} catch (const BadRequiredStrength& ex) {
static const constexpr KiwiErr err {
KiwiErrBadRequiredStrength,
"A required strength cannot be used in this context."};
return &err;
} catch (const InternalSolverError& ex) {
static const constexpr KiwiErr base {
KiwiErrInternalSolverError,
"An internal solver error occurred."};
return new_error(&base, ex);
} catch (std::bad_alloc&) {
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
return &err;
} catch (const std::exception& ex) {
return new_error(&kKiwiErrUnhandledCxxException, ex);
} catch (...) {
return &kKiwiErrUnhandledCxxException;
}
return nullptr;
}
template<typename P, typename R, typename F>
inline KiwiErrPtr wrap_err(P ptr, F&& f) {
if (!ptr) {
return &kKiwiErrNullObjectArg0;
}
return wrap_err([&]() { f(ptr); });
}
template<typename P, typename R, typename F>
inline KiwiErrPtr wrap_err(P ptr, R ref, F&& f) {
if (!ptr) {
return &kKiwiErrNullObjectArg0;
} else if (!ref) {
return &kKiwiErrNullObjectArg1;
}
return wrap_err([&]() { f(ptr, ref); });
}
} // namespace
extern "C" {
KiwiVarRef kiwi_var_new(const char* name) {
return to_var_cref(name ? name : "");
}
void kiwi_var_del(KiwiVarRef var) {
VariableRef(var).destroy();
}
const char* kiwi_var_name(KiwiVarRef var) {
const VariableRef self(var);
return self ? self->name().c_str() : "(<null>)";
}
void kiwi_var_set_name(KiwiVarRef var, const char* name) {
VariableRef self(var);
if (self)
self->setName(name);
}
double kiwi_var_value(KiwiVarRef var) {
const VariableRef self(var);
return self ? self->value() : std::numeric_limits<double>::quiet_NaN();
}
void kiwi_var_set_value(KiwiVarRef var, double value) {
VariableRef self(var);
if (self)
self->setValue(value);
}
int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) {
VariableRef self(var); // const defect in upstream
const VariableRef other_ref(other);
return self && other_ref && self->equals(other_ref);
}
KiwiConstraintRef
kiwi_constraint_new(KiwiExpressionConstPtr expression, enum KiwiRelOp op, double strength) {
if (strength < 0.0) {
strength = kiwi::strength::required;
}
std::vector<Term> terms;
if (expression) {
terms.reserve(expression->term_count);
for (auto* t = expression->terms; t != expression->terms + expression->term_count; ++t) {
ConstVariableRef var(t->var);
if (var)
terms.emplace_back(var.cref(), t->coefficient);
}
}
return to_constraint_cref(
Expression(std::move(terms), expression->constant),
static_cast<RelationalOperator>(op),
strength
);
}
void kiwi_constraint_del(KiwiConstraintRef constraint) {
ConstraintRef(constraint).destroy();
}
double kiwi_constraint_strength(KiwiConstraintRef constraint) {
const ConstraintRef self(constraint);
return self ? self->strength() : std::numeric_limits<double>::quiet_NaN();
}
enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint) {
const ConstraintRef self(constraint);
return self ? static_cast<KiwiRelOp>(self->op()) : KiwiRelOp::KIWI_OP_EQ;
}
bool kiwi_constraint_violated(KiwiConstraintRef constraint) {
const ConstraintRef self(constraint);
return self ? self->violated() : 0;
}
int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size) {
const ConstraintRef self(constraint);
if (!self)
return 0;
const auto& expr = self->expression();
const auto& terms = expr.terms();
const int n_terms = terms.size();
if (!out || out_size < n_terms)
return n_terms;
auto* p = out->terms;
for (const auto& t : terms) {
*p = KiwiTerm {to_var_cref(t.variable()), t.coefficient()};
++p;
}
out->term_count = p - out->terms;
out->constant = expr.constant();
return n_terms;
}
KiwiSolverPtr kiwi_solver_new() {
return reinterpret_cast<KiwiSolverPtr>(new (std::nothrow) Solver());
}
void kiwi_solver_del(KiwiSolverPtr sp) {
auto* solver = reinterpret_cast<Solver*>(sp);
if (solver)
delete solver;
}
KiwiErrPtr kiwi_solver_add_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
return wrap_err(
reinterpret_cast<Solver*>(s),
ConstraintRef(constraint),
[](auto solver, const auto c) { solver->addConstraint(c); }
);
}
KiwiErrPtr kiwi_solver_remove_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
return wrap_err(
reinterpret_cast<Solver*>(s),
ConstraintRef(constraint),
[](auto solver, const auto c) { solver->removeConstraint(c); }
);
}
bool kiwi_solver_has_constraint(KiwiSolverPtr s, KiwiConstraintRef constraint) {
const auto* solver = reinterpret_cast<Solver*>(s);
ConstraintRef c(constraint);
if (!solver || !c)
return 0;
return solver->hasConstraint(c);
}
KiwiErrPtr kiwi_solver_add_edit_var(KiwiSolverPtr s, KiwiVarRef var, double strength) {
return wrap_err(
reinterpret_cast<Solver*>(s),
VariableRef(var),
[strength](auto solver, const auto v) { solver->addEditVariable(v, strength); }
);
}
KiwiErrPtr kiwi_solver_remove_edit_var(KiwiSolverPtr s, KiwiVarRef var) {
return wrap_err(reinterpret_cast<Solver*>(s), VariableRef(var), [](auto solver, const auto v) {
solver->removeEditVariable(v);
});
}
bool kiwi_solver_has_edit_var(KiwiSolverPtr s, KiwiVarRef var) {
const auto* solver = reinterpret_cast<Solver*>(s);
VariableRef v(var);
if (!solver || !v)
return 0;
return solver->hasEditVariable(v);
}
KiwiErrPtr kiwi_solver_suggest_value(KiwiSolverPtr s, KiwiVarRef var, double value) {
return wrap_err(
reinterpret_cast<Solver*>(s),
VariableRef(var),
[value](auto solver, const auto v) { solver->suggestValue(v, value); }
);
}
void kiwi_solver_update_vars(KiwiSolverPtr s) {
auto* solver = reinterpret_cast<Solver*>(s);
if (solver)
solver->updateVariables();
}
void kiwi_solver_reset(KiwiSolverPtr s) {
auto* solver = reinterpret_cast<Solver*>(s);
if (solver)
solver->reset();
}
void kiwi_solver_dump(KiwiSolverPtr s) {
auto* solver = reinterpret_cast<Solver*>(s);
if (solver)
solver->dump();
}
char* kiwi_solver_dumps(KiwiSolverPtr s) {
auto* solver = reinterpret_cast<Solver*>(s);
if (!solver)
return nullptr;
const auto str = solver->dumps();
const auto buf_size = str.size() + 1;
auto* buf = static_cast<char*>(std::malloc(buf_size));
if (!buf)
return nullptr;
std::memcpy(buf, str.c_str(), str.size() + 1);
return buf;
}
} // extern "C"