Skip to content

Commit 97386a3

Browse files
committed
fix(interop): Correctly initialize pointers from numbers on 64-bit archs
JavaScript represents 64-bit integers as floating point `double` values. This means that the conversion from JSValue to pointer is done by extracting the `double` value and then converting it to integer. **Caution:** This means that pointers with more than 54 significant bits are likely to be rounded and misrepresented! However, current OS and hardware implementations are using 48 bits, so we're safe at the time being. See https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details and https://en.wikipedia.org/wiki/ARM_architecture#ARMv8-A
1 parent 4dde0e4 commit 97386a3

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/NativeScript/Marshalling/Pointer/PointerConstructor.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,24 @@ void PointerConstructor::finishCreation(VM& vm, PointerPrototype* pointerPrototy
3333
EncodedJSValue JSC_HOST_CALL PointerConstructor::constructPointerInstance(ExecState* execState) {
3434
void* value = nullptr;
3535
if (execState->argumentCount() == 1) {
36-
value = reinterpret_cast<void*>(execState->argument(0).toUInt32(execState));
36+
auto arg0 = execState->argument(0);
37+
38+
if (!arg0.isAnyInt()) {
39+
auto scope = DECLARE_THROW_SCOPE(execState->vm());
40+
return throwVMError(execState, scope, createError(execState, "Pointer constructor's first arg must be an integer."_s));
41+
}
42+
#if __SIZEOF_POINTER__ == 8
43+
// JSC stores 64-bit integers as doubles in JSValue.
44+
// Caution: This means that pointers with more than 54 significant bits
45+
// are likely to be rounded and misrepresented!
46+
// However, current OS and hardware implementations are using 48 bits,
47+
// so we're safe at the time being.
48+
// See https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details
49+
// and https://en.wikipedia.org/wiki/ARM_architecture#ARMv8-A
50+
value = reinterpret_cast<void*>(arg0.asAnyInt());
51+
#else
52+
value = reinterpret_cast<void*>(arg0.toInt32(execState));
53+
#endif
3754
}
3855

3956
JSValue result = jsCast<GlobalObject*>(execState->lexicalGlobalObject())->interop()->pointerInstanceForPointer(execState, value);

tests/TestRunner/app/Marshalling/TypesTests.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
function pointerTo(type, value) {
2+
var outerPtr = interop.alloc(interop.sizeof(interop.Pointer));
3+
var outerRef = new interop.Reference(type, outerPtr);
4+
outerRef.value = value;
5+
return outerPtr;
6+
}
7+
8+
function referenceFromPointerNumber(type, value) {
9+
const ptr = new interop.Pointer(value);
10+
const ptrToPtr = pointerTo(interop.Pointer, ptr);
11+
return new interop.Reference(type, ptrToPtr);
12+
}
13+
14+
function valueFromPointerNumber(type, value) {
15+
return referenceFromPointerNumber(type, value).value;
16+
}
17+
118
describe(module.id, function () {
219
afterEach(function () {
320
TNSClearOutput();
@@ -79,13 +96,6 @@ describe(module.id, function () {
7996
expect(interop.sizeof(interop.types.selector)).toBe(interop.sizeof(interop.Pointer));
8097
});
8198

82-
function pointerTo(type, value) {
83-
var outerPtr = interop.alloc(interop.sizeof(interop.Pointer));
84-
var outerRef = new interop.Reference(type, outerPtr);
85-
outerRef.value = value;
86-
return outerPtr;
87-
}
88-
8999
it("ReferenceType", function () {
90100
var ptr = interop.alloc(2 * interop.sizeof(interop.types.id));
91101
var ref = new interop.Reference(new interop.types.ReferenceType(interop.types.int32), ptr);
@@ -198,4 +208,27 @@ describe(module.id, function () {
198208
var result = interop.types.double(doublePtr);
199209
expect(result === number).toBe(true);
200210
});
211+
212+
it("Initialize pointer from non-int throws", function () {
213+
expect(() => new interop.Pointer("")).toThrowError(/must be an integer/);
214+
});
215+
216+
it("Initialize reference from pointer", function () {
217+
// Create a native NSDictionary holding the pair "value" -> "key"
218+
const key = "key";
219+
const value = "value";
220+
const d1 = NSDictionary.dictionaryWithObjectForKey(value, key);
221+
222+
expect(d1.valueForKey(key)).toBe(value, "Dictionary should return initial value string.");
223+
224+
// Take the address of the NSDictionary
225+
const addr = interop.handleof(d1).toNumber();
226+
// Create a new native wrapper using the address
227+
const d2 = valueFromPointerNumber(NSDictionary, addr);
228+
// Take the address of the new wrapper
229+
const addr2 = interop.handleof(d2).toNumber();
230+
231+
expect(addr2).toBe(addr, "The new object should have the same address.");
232+
expect(d2.valueForKey(key)).toEqual(d1.valueForKey(key), "Returned values should be equal");
233+
});
201234
});

0 commit comments

Comments
 (0)