Skip to content

Commit

Permalink
fix(interop): Add methods toHexString and toDecimalString to Pointer
Browse files Browse the repository at this point in the history
This allows for correct serialization of pointers through strings. Which can be
used for passing native objects to workers as discussed in #620 (comment).
The need for these methods arises from the fact that `toNumber` can't
be used for negative pointer values, due to JavaScript's values
inability to represent very large and/or negative 64-bit integer values
as integers.

There is a real-world use-case for the value of `-1` shown in #921
  • Loading branch information
mbektchiev committed Jan 31, 2019
1 parent 8d69895 commit 8b4157f
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/NativeScript/Marshalling/Pointer/PointerPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ static EncodedJSValue JSC_HOST_CALL pointerProtoFuncToString(ExecState* execStat
return JSValue::encode(result);
}

static EncodedJSValue JSC_HOST_CALL pointerProtoFuncToHexString(ExecState* execState) {
PointerInstance* pointer = jsCast<PointerInstance*>(execState->thisValue());
const void* value = pointer->data();
JSValue result = jsString(execState, WTF::String::format("%p", value));
return JSValue::encode(result);
}

static EncodedJSValue JSC_HOST_CALL pointerProtoFuncToDecimalString(ExecState* execState) {
PointerInstance* pointer = jsCast<PointerInstance*>(execState->thisValue());
const void* value = pointer->data();
JSValue result = jsString(execState, WTF::String::format("%ld", static_cast<intptr_t>(reinterpret_cast<size_t>(value))));
return JSValue::encode(result);
}

static EncodedJSValue JSC_HOST_CALL pointerProtoFuncToNumber(ExecState* execState) {
PointerInstance* pointer = jsCast<PointerInstance*>(execState->thisValue());
const void* value = pointer->data();
Expand All @@ -56,5 +70,7 @@ void PointerPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) {
this->putDirectNativeFunction(vm, globalObject, Identifier::fromString(globalObject->globalExec(), "subtract"), 1, pointerProtoFuncSubtract, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
this->putDirectNativeFunction(vm, globalObject, vm.propertyNames->toString, 0, pointerProtoFuncToString, NoIntrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum));
this->putDirectNativeFunction(vm, globalObject, Identifier::fromString(globalObject->globalExec(), "toNumber"), 0, pointerProtoFuncToNumber, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
this->putDirectNativeFunction(vm, globalObject, Identifier::fromString(globalObject->globalExec(), "toHexString"), 0, pointerProtoFuncToHexString, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
this->putDirectNativeFunction(vm, globalObject, Identifier::fromString(globalObject->globalExec(), "toDecimalString"), 0, pointerProtoFuncToDecimalString, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
}
} // namespace NativeScript
48 changes: 48 additions & 0 deletions tests/TestRunner/app/Marshalling/PointerTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,54 @@ describe(module.id, function () {
expect(pointer instanceof interop.Pointer).toBe(true);
expect(pointer.toNumber()).toBe(number);
expect(pointer.toString()).toBe(`<Pointer: 0x${number.toString(16)}>`);
expect(pointer.toDecimalString()).toBe(number.toString());
});

it("Pointer limits and construction from the decimal representation of another pointer", function () {
const additions = [0, 3, 5, 100, Math.pow(2, 10)];
let numbers = [1, 2, Math.pow(2, 20), Math.pow(2, 31)];
if (interop.sizeof(interop.types.id) > 4) {
//64-bit pointers up to 2^50 should work correctly
numbers = numbers.concat([Math.pow(2, 40), Math.pow(2, 50)]);
}
numbers.forEach((num, idx) => {
additions.forEach(add => {
// add and subtract each addend
for (let addFactor = -1; addFactor <= 1; addFactor += 2) {
// try both positive and negative values
for (let factor = -1; factor <= 1; factor += 2) {
// Create a pointer from the calculated number
const number = (num + add * addFactor) * factor;
let expectedDecimal = number;
if (interop.sizeof(interop.types.id) == 4) {
// Handle 32-bit architecture overflows in pointer.toDecimalString
if (number >= 0x80000000) {
expectedDecimal = number - 0x100000000;
} else if (number < -0x80000000) {
expectedDecimal = number - -0x100000000;
}
}
const p = new interop.Pointer(number);
// console.log(idx + 1, number, p, p.toDecimalString(), new Number(p.toDecimalString()), `${add * addFactor}`);

// Create another pointer from the decimal representation of the previous number
// This is the only way (we're aware of) that allows to accurately tranfer
// any valid 64-bit pointer (and it's negative value) as a string to a worker
// (toNumber converts negative values to `double`s because they are treated as
// very large unsigned 64-bit integers. As such they can no longer be represented
// in a JSValue as integers.)
const pointer = new interop.Pointer(new Number(p.toDecimalString()));
expect(pointer instanceof interop.Pointer).toBe(true);
expect(pointer.toDecimalString()).toBe(expectedDecimal.toString(10), "Decimal string mismatch");
if (number > 0) {
expect(pointer.toNumber()).toBe(number, "Number mismatch");
expect(pointer.toString()).toBe(`<Pointer: 0x${number.toString(16)}>`, "String mismatch");
expect(pointer.toHexString()).toBe(`0x${number.toString(16)}`, "Hex string mismatch");
}
}
}
});
});
});

it("PointerArithmetic", function () {
Expand Down

0 comments on commit 8b4157f

Please sign in to comment.