1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ScaffoldingConverter_h
#define mozilla_ScaffoldingConverter_h
#include <limits>
#include <type_traits>
#include "nsString.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/dom/OwnedRustBuffer.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/UniFFIBinding.h"
#include "mozilla/dom/UniFFIPointer.h"
#include "mozilla/dom/UniFFIPointerType.h"
#include "mozilla/dom/UniFFIRust.h"
#include "mozilla/dom/UniFFIScaffolding.h"
namespace mozilla::uniffi {
// Handle converting types between JS and Rust
//
// Scaffolding conversions are done using a 2 step process:
// - Call FromJs/FromRust to convert to an intermediate type
// - Call IntoJs/IntoRust to convert from that type to the target type
//
// The main reason for this is handling RustBuffers when other arguments fail
// to convert. By using OwnedRustBuffer as the intermediate type, we can
// ensure those buffers get freed in that case. Note that we can't use
// OwnedRustBuffer as the Rust type. Passing the buffer into Rust transfers
// ownership so we shouldn't free the buffer in this case.
//
// For most other types, we just use the Rust type as the intermediate type.
template <typename T>
class ScaffoldingConverter {
public:
using RustType = T;
using IntermediateType = T;
// Convert a JS value to an intermedate type
//
// This inputs a const ref, because that's what the WebIDL bindings send to
// us.
//
// If this succeeds then IntoRust is also guaranteed to succeed
static mozilla::Result<IntermediateType, nsCString> FromJs(
const dom::UniFFIScaffoldingValue& aValue) {
if (!aValue.IsDouble()) {
return Err("Bad argument type"_ns);
}
double value = aValue.GetAsDouble();
if (std::isnan(value)) {
return Err("NaN not allowed"_ns);
}
if constexpr (std::is_integral<RustType>::value) {
// Use PrimitiveConversionTraits_Limits rather than std::numeric_limits,
// since it handles JS-specific bounds like the 64-bit integer limits.
// (see Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER)
if (value < dom::PrimitiveConversionTraits_Limits<RustType>::min() ||
value > dom::PrimitiveConversionTraits_Limits<RustType>::max()) {
return Err("Out of bounds"_ns);
}
}
// Don't check float bounds for a few reasons.
// - It's difficult because
// PrimitiveConversionTraits_Limits<float>::min() is the smallest
// positive value, rather than the most negative.
// - A float value unlikely to overflow
// - It's also likely that we can't do an exact conversion because the
// float doesn't have enough precision, but it doesn't seem correct
// to error out in that case.
RustType rv = static_cast<RustType>(value);
if constexpr (std::is_integral<RustType>::value) {
if (rv != value) {
return Err("Not an integer"_ns);
}
}
return rv;
}
// Convert an intermediate type to a Rust type
//
// IntoRust doesn't touch the JS data, so it's safe to call in a worker thread
static RustType IntoRust(IntermediateType aValue) { return aValue; }
// Convert an Rust type to an intermediate type
//
// This inputs a value since RustTypes are POD types
//
// If this succeeds then IntoJs is also guaranteed to succeed
static mozilla::Result<IntermediateType, nsCString> FromRust(
RustType aValue) {
if constexpr (std::is_same<RustType, int64_t>::value ||
std::is_same<RustType, uint64_t>::value) {
// Check that the value can fit in a double (only needed for 64 bit types)
if (aValue < dom::PrimitiveConversionTraits_Limits<RustType>::min() ||
aValue > dom::PrimitiveConversionTraits_Limits<RustType>::max()) {
return Err("Out of bounds"_ns);
}
}
if constexpr (std::is_floating_point<RustType>::value) {
if (std::isnan(aValue)) {
return Err("NaN not allowed"_ns);
}
}
return aValue;
}
// Convert an intermedate type to a JS type
//
// This inputs an r-value reference since we may want to move data out of
// this type.
static void IntoJs(JSContext* aContext, IntermediateType&& aValue,
dom::UniFFIScaffoldingValue& aDest) {
aDest.SetAsDouble() = aValue;
}
};
template <>
class ScaffoldingConverter<RustBuffer> {
public:
using RustType = RustBuffer;
using IntermediateType = OwnedRustBuffer;
static mozilla::Result<OwnedRustBuffer, nsCString> FromJs(
const dom::UniFFIScaffoldingValue& aValue) {
if (!aValue.IsArrayBuffer()) {
return Err("Bad argument type"_ns);
}
return OwnedRustBuffer::FromArrayBuffer(aValue.GetAsArrayBuffer());
}
static RustBuffer IntoRust(OwnedRustBuffer&& aValue) {
return aValue.IntoRustBuffer();
}
static mozilla::Result<OwnedRustBuffer, nsCString> FromRust(
RustBuffer aValue) {
return OwnedRustBuffer(aValue);
}
static void IntoJs(JSContext* aContext, OwnedRustBuffer&& aValue,
dom::UniFFIScaffoldingValue& aDest) {
aDest.SetAsArrayBuffer().Init(aValue.IntoArrayBuffer(aContext));
}
};
// ScaffoldingConverter for object pointers
template <const UniFFIPointerType* PointerType>
class ScaffoldingObjectConverter {
public:
using RustType = void*;
using IntermediateType = void*;
static mozilla::Result<void*, nsCString> FromJs(
const dom::UniFFIScaffoldingValue& aValue) {
if (!aValue.IsUniFFIPointer()) {
return Err("Bad argument type"_ns);
}
dom::UniFFIPointer& value = aValue.GetAsUniFFIPointer();
if (!value.IsSamePtrType(PointerType)) {
return Err("Bad pointer type"_ns);
}
return value.ClonePtr();
}
static void* IntoRust(void* aValue) { return aValue; }
static mozilla::Result<void*, nsCString> FromRust(void* aValue) {
return aValue;
}
static void IntoJs(JSContext* aContext, void* aValue,
dom::UniFFIScaffoldingValue& aDest) {
aDest.SetAsUniFFIPointer() =
dom::UniFFIPointer::Create(aValue, PointerType);
}
};
// ScaffoldingConverter for void returns
//
// This doesn't implement the normal interface, it's only use is a the
// ReturnConverter parameter of ScaffoldingCallHandler.
template <>
class ScaffoldingConverter<void> {
public:
using RustType = void;
};
} // namespace mozilla::uniffi
#endif // mozilla_ScaffoldingConverter_h
|