aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsapi
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-12-20 12:31:52 +0100
committerUlf Hermann <ulf.hermann@qt.io>2023-01-12 10:49:10 +0100
commitc7722d4ed61d6a887e9f6c403ffa10b2048de2a4 (patch)
tree2d01090de983e4a354eb0359888aad0b78a26a08 /src/qml/jsapi
parentc333d4108da6d3db06c17142226c28e14e89703f (diff)
Change value encoding scheme to make space for larger pointers
On android and on some other platforms, the upper bits of a pointer are significant. We need to store them in our JS value encoding. Shift the bits around to make this happen. We now can store pointers of up to 57 bits. That's enough for everything we've seen so far. Fixes: QTBUG-101686 Fixes: QTBUG-91150 Pick-to: 6.5 Change-Id: I72e0fe63b27fca94840f82963e4d3936b3581b28 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io> Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
Diffstat (limited to 'src/qml/jsapi')
-rw-r--r--src/qml/jsapi/qjsvalue.cpp101
-rw-r--r--src/qml/jsapi/qjsvalue_p.h280
2 files changed, 270 insertions, 111 deletions
diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp
index 7306d00d6a..0aaa471e23 100644
--- a/src/qml/jsapi/qjsvalue.cpp
+++ b/src/qml/jsapi/qjsvalue.cpp
@@ -178,61 +178,59 @@ using namespace QV4;
/*!
Constructs a new QJSValue with a boolean \a value.
*/
-QJSValue::QJSValue(bool value) : d(QV4::Encode(value))
+QJSValue::QJSValue(bool value) : d(QJSValuePrivate::encode(value))
{
}
/*!
Constructs a new QJSValue with a number \a value.
*/
-QJSValue::QJSValue(int value) : d(QV4::Encode(value))
+QJSValue::QJSValue(int value) : d(QJSValuePrivate::encode(value))
{
}
/*!
Constructs a new QJSValue with a number \a value.
*/
-QJSValue::QJSValue(uint value) : d(QV4::Encode(value))
+QJSValue::QJSValue(uint value) : d(QJSValuePrivate::encode(value))
{
}
/*!
Constructs a new QJSValue with a number \a value.
*/
-QJSValue::QJSValue(double value) : d(QV4::Encode(value))
+QJSValue::QJSValue(double value) : d(QJSValuePrivate::encode(value))
{
}
/*!
Constructs a new QJSValue with a string \a value.
*/
-QJSValue::QJSValue(const QString& value)
+QJSValue::QJSValue(const QString &value) : d(QJSValuePrivate::encode(value))
{
- QJSValuePrivate::setString(this, QString(value));
}
/*!
Constructs a new QJSValue with a special \a value.
*/
-QJSValue::QJSValue(SpecialValue value) : d(value == NullValue ? QV4::Encode::null() : 0)
+QJSValue::QJSValue(SpecialValue value)
+ : d(value == NullValue ? QJSValuePrivate::encodeNull() : QJSValuePrivate::encodeUndefined())
{
}
/*!
Constructs a new QJSValue with a string \a value.
*/
-QJSValue::QJSValue(const QLatin1String &value)
+QJSValue::QJSValue(const QLatin1String &value) : d(QJSValuePrivate::encode(value))
{
- QJSValuePrivate::setString(this, QString(value));
}
/*!
Constructs a new QJSValue with a string \a value.
*/
#ifndef QT_NO_CAST_FROM_ASCII
-QJSValue::QJSValue(const char *value)
+QJSValue::QJSValue(const char *value) : d(QJSValuePrivate::encode(QString::fromUtf8(value)))
{
- QJSValuePrivate::setString(this, QString(QString::fromUtf8(value)));
}
#endif
@@ -243,12 +241,24 @@ QJSValue::QJSValue(const char *value)
true), then only a reference to the underlying object is copied into
the new script value (i.e., the object itself is not copied).
*/
-QJSValue::QJSValue(const QJSValue &other) : d(0)
+QJSValue::QJSValue(const QJSValue &other) : d(other.d)
{
- if (const QString *string = QJSValuePrivate::asQString(&other))
- QJSValuePrivate::setString(this, *string);
- else
- QJSValuePrivate::setValue(this, QJSValuePrivate::asReturnedValue(&other));
+ switch (QJSValuePrivate::tag(d)) {
+ case QJSValuePrivate::Kind::Undefined:
+ case QJSValuePrivate::Kind::Null:
+ case QJSValuePrivate::Kind::IntValue:
+ case QJSValuePrivate::Kind::BoolValue:
+ return;
+ case QJSValuePrivate::Kind::DoublePtr:
+ d = QJSValuePrivate::encode(*QJSValuePrivate::doublePtr(d));
+ return;
+ case QJSValuePrivate::Kind::QV4ValuePtr:
+ d = QJSValuePrivate::encode(*QJSValuePrivate::qv4ValuePtr(d));
+ return;
+ case QJSValuePrivate::Kind::QStringPtr:
+ d = QJSValuePrivate::encode(*QJSValuePrivate::qStringPtr(d));
+ break;
+ }
}
/*!
@@ -279,7 +289,7 @@ QJSValue::~QJSValue()
*/
bool QJSValue::isBool() const
{
- return QV4::Value::fromReturnedValue(d).isBoolean();
+ return QJSValuePrivate::tag(d) == QJSValuePrivate::Kind::BoolValue;
}
/*!
@@ -290,7 +300,15 @@ bool QJSValue::isBool() const
*/
bool QJSValue::isNumber() const
{
- return QV4::Value::fromReturnedValue(d).isNumber();
+ switch (QJSValuePrivate::tag(d)) {
+ case QJSValuePrivate::Kind::IntValue:
+ case QJSValuePrivate::Kind::DoublePtr:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
}
/*!
@@ -299,7 +317,7 @@ bool QJSValue::isNumber() const
*/
bool QJSValue::isNull() const
{
- return QV4::Value::fromReturnedValue(d).isNull();
+ return QJSValuePrivate::tag(d) == QJSValuePrivate::Kind::Null;
}
/*!
@@ -310,11 +328,17 @@ bool QJSValue::isNull() const
*/
bool QJSValue::isString() const
{
- if (QJSValuePrivate::asQString(this))
+ switch (QJSValuePrivate::tag(d)) {
+ case QJSValuePrivate::Kind::QStringPtr:
return true;
+ case QJSValuePrivate::Kind::QV4ValuePtr: {
+ return QJSValuePrivate::qv4ValuePtr(d)->isString();
+ }
+ default:
+ break;
+ }
- // String is managed
- return QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)).isString();
+ return false;
}
/*!
@@ -323,14 +347,16 @@ bool QJSValue::isString() const
*/
bool QJSValue::isUndefined() const
{
- if (QJSValuePrivate::asQString(this))
- return false;
- QV4::Value v = QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this));
- if (v.isUndefined())
+ switch (QJSValuePrivate::tag(d)) {
+ case QJSValuePrivate::Kind::Undefined:
return true;
- if (!v.isManaged())
- return false;
- return v.managed() == nullptr;
+ case QJSValuePrivate::Kind::QV4ValuePtr:
+ return QJSValuePrivate::qv4ValuePtr(d)->isUndefined();
+ default:
+ break;
+ }
+
+ return false;
}
/*!
@@ -867,22 +893,22 @@ QJSValue::QJSValue(QJSPrimitiveValue &&value)
{
switch (value.type()) {
case QJSPrimitiveValue::Undefined:
- d = QV4::Encode::undefined();
+ d = QJSValuePrivate::encodeUndefined();
return;
case QJSPrimitiveValue::Null:
- d = QV4::Encode::null();
+ d = QJSValuePrivate::encodeNull();
return;
case QJSPrimitiveValue::Boolean:
- d = QV4::Encode(value.asBoolean());
+ d = QJSValuePrivate::encode(value.asBoolean());
return;
case QJSPrimitiveValue::Integer:
- d = QV4::Encode(value.asInteger());
+ d = QJSValuePrivate::encode(value.asInteger());
return;
case QJSPrimitiveValue::Double:
- d = QV4::Encode(value.asDouble());
+ d = QJSValuePrivate::encode(value.asDouble());
return;
case QJSPrimitiveValue::String:
- QJSValuePrivate::setString(this, value.asString());
+ d = QJSValuePrivate::encode(value.asString());
return;
}
@@ -894,10 +920,11 @@ QJSValue::QJSValue(QJSManagedValue &&value)
if (!value.d) {
d = QV4::Encode::undefined();
} else if (value.d->isManaged()) {
- QJSValuePrivate::setRawValue(this, value.d);
+ // If it's managed, we can adopt the persistent value.
+ QJSValuePrivate::adoptPersistentValue(this, value.d);
value.d = nullptr;
} else {
- d = value.d->asReturnedValue();
+ d = QJSValuePrivate::encode(*value.d);
QV4::PersistentValueStorage::free(value.d);
value.d = nullptr;
}
diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h
index 0e9c31909f..b95e76e76e 100644
--- a/src/qml/jsapi/qjsvalue_p.h
+++ b/src/qml/jsapi/qjsvalue_p.h
@@ -27,61 +27,163 @@
QT_BEGIN_NAMESPACE
-// QJSValue::d is a QV4::ReturnedValue, but we don't want to expose that in the public header.
-// We use the lower bits of the managed pointer to hide a QString* or a QV4::Value* in there.
-Q_STATIC_ASSERT(sizeof(QV4::ReturnedValue) == sizeof(quint64));
-Q_STATIC_ASSERT(alignof(QV4::Value) >= 4);
-Q_STATIC_ASSERT(alignof(QString) >= 4);
-
-enum PointerMask: quintptr {
- IsV4Value = 0x0,
- IsString = 0x1
-};
-
class QJSValuePrivate
{
- static const QV4::Value *managedValue(const QV4::Value &v)
+ static constexpr quint64 s_tagBits = 3; // 3 bits mask
+ static constexpr quint64 s_tagMask = (1 << s_tagBits) - 1;
+
+ static constexpr quint64 s_pointerBit = 0x1;
+
+public:
+ enum class Kind {
+ Undefined = 0x0,
+ Null = 0x2,
+ IntValue = 0x4,
+ BoolValue = 0x6,
+ DoublePtr = 0x0 | s_pointerBit,
+ QV4ValuePtr = 0x2 | s_pointerBit,
+ QStringPtr = 0x4 | s_pointerBit,
+ };
+
+ static_assert(quint64(Kind::Undefined) <= s_tagMask);
+ static_assert(quint64(Kind::Null) <= s_tagMask);
+ static_assert(quint64(Kind::IntValue) <= s_tagMask);
+ static_assert(quint64(Kind::BoolValue) <= s_tagMask);
+ static_assert(quint64(Kind::DoublePtr) <= s_tagMask);
+ static_assert(quint64(Kind::QV4ValuePtr) <= s_tagMask);
+ static_assert(quint64(Kind::QStringPtr) <= s_tagMask);
+
+ static Kind tag(quint64 raw) { return Kind(raw & s_tagMask); }
+
+#if QT_POINTER_SIZE == 4
+ static void *pointer(quint64 raw)
{
- const quintptr m = quintptr(v.m());
- return (m & IsString) ? nullptr : reinterpret_cast<QV4::Value *>(m);
+ Q_ASSERT(quint64(tag(raw)) & s_pointerBit);
+ return reinterpret_cast<void *>(raw >> 32);
}
- static QV4::Value *managedValue(QV4::Value &v)
+ static quint64 encodePointer(void *pointer, Kind tag)
{
- quintptr m = quintptr(v.m());
- return (m & IsString) ? nullptr : reinterpret_cast<QV4::Value *>(m);
+ Q_ASSERT(quint64(tag) & s_pointerBit);
+ return (quint64(quintptr(pointer)) << 32) | quint64(tag);
}
+#else
+ static constexpr quint64 s_minAlignment = 1 << s_tagBits;
+ static_assert(alignof(double) >= s_minAlignment);
+ static_assert(alignof(QV4::Value) >= s_minAlignment);
+ static_assert(alignof(QString) >= s_minAlignment);
- static const QString *qstring(const QV4::Value &v)
+ static void *pointer(quint64 raw)
{
- const quintptr m = quintptr(v.m());
- return (m & IsString) ? reinterpret_cast<QString *>(m & ~IsString) : nullptr;
+ Q_ASSERT(quint64(tag(raw)) & s_pointerBit);
+ return reinterpret_cast<void *>(raw & ~s_tagMask);
}
- static QV4::ReturnedValue encode(QString string)
+ static quint64 encodePointer(void *pointer, Kind tag)
{
- const quintptr m = quintptr(new QString(std::move(string))) | IsString;
- return encodeRawValue(m);
+ Q_ASSERT(quint64(tag) & s_pointerBit);
+ return quintptr(pointer) | quint64(tag);
}
+#endif
- static QV4::ReturnedValue encode(const QV4::Value &managedValue)
+ static quint64 encodeUndefined()
{
- QV4::Value *m = managedValue.as<QV4::Managed>()->engine()
- ->memoryManager->m_persistentValues->allocate();
- *m = managedValue;
- return encodeRawValue(quintptr(m));
+ return quint64(Kind::Undefined);
}
- static QV4::ReturnedValue encodeRawValue(quintptr m)
+ static quint64 encodeNull()
{
- return QV4::Value::fromHeapObject(reinterpret_cast<QV4::Heap::Base *>(m)).asReturnedValue();
+ return quint64(Kind::Null);
}
-public:
+ static int intValue(quint64 v)
+ {
+ Q_ASSERT(tag(v) == Kind::IntValue);
+ return v >> 32;
+ }
+
+ static quint64 encode(int intValue)
+ {
+ return (quint64(intValue) << 32) | quint64(Kind::IntValue);
+ }
+
+ static quint64 encode(uint uintValue)
+ {
+ return (uintValue < uint(std::numeric_limits<int>::max()))
+ ? encode(int(uintValue))
+ : encode(double(uintValue));
+ }
- static void setRawValue(QJSValue *jsval, QV4::Value *m)
+ static bool boolValue(quint64 v)
{
- jsval->d = encodeRawValue(quintptr(m));
+ Q_ASSERT(tag(v) == Kind::BoolValue);
+ return v >> 32;
+ }
+
+ static quint64 encode(bool boolValue)
+ {
+ return (quint64(boolValue) << 32) | quint64(Kind::BoolValue);
+ }
+
+ static double *doublePtr(quint64 v)
+ {
+ Q_ASSERT(tag(v) == Kind::DoublePtr);
+ return static_cast<double *>(pointer(v));
+ }
+
+ static quint64 encode(double doubleValue)
+ {
+ return encodePointer(new double(doubleValue), Kind::DoublePtr);
+ }
+
+ static QV4::Value *qv4ValuePtr(quint64 v)
+ {
+ Q_ASSERT(tag(v) == Kind::QV4ValuePtr);
+ return static_cast<QV4::Value *>(pointer(v));
+ }
+
+ static quint64 encode(const QV4::Value &qv4Value)
+ {
+ switch (qv4Value.type()) {
+ case QV4::StaticValue::Boolean_Type:
+ return encode(qv4Value.booleanValue());
+ case QV4::StaticValue::Integer_Type:
+ return encode(qv4Value.integerValue());
+ case QV4::StaticValue::Managed_Type: {
+ QV4::Value *m = qv4Value.as<QV4::Managed>()->engine()
+ ->memoryManager->m_persistentValues->allocate();
+ Q_ASSERT(m);
+ *m = qv4Value;
+ return encodePointer(m, Kind::QV4ValuePtr);
+ }
+ case QV4::StaticValue::Double_Type:
+ return encode(qv4Value.doubleValue());
+ case QV4::StaticValue::Null_Type:
+ return encodeNull();
+ case QV4::StaticValue::Empty_Type:
+ Q_UNREACHABLE();
+ break;
+ case QV4::StaticValue::Undefined_Type:
+ break;
+ }
+
+ return encodeUndefined();
+ }
+
+ static QString *qStringPtr(quint64 v)
+ {
+ Q_ASSERT(tag(v) == Kind::QStringPtr);
+ return static_cast<QString *>(pointer(v));
+ }
+
+ static quint64 encode(QString stringValue)
+ {
+ return encodePointer(new QString(std::move(stringValue)), Kind::QStringPtr);
+ }
+
+ static quint64 encode(QLatin1String stringValue)
+ {
+ return encodePointer(new QString(std::move(stringValue)), Kind::QStringPtr);
}
static QJSValue fromReturnedValue(QV4::ReturnedValue d)
@@ -94,49 +196,73 @@ public:
template<typename T>
static const T *asManagedType(const QJSValue *jsval)
{
- const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- if (!v.isManaged())
- return nullptr;
- if (const QV4::Value *value = managedValue(v))
- return value->as<T>();
+ if (tag(jsval->d) == Kind::QV4ValuePtr) {
+ if (const QV4::Value *value = qv4ValuePtr(jsval->d))
+ return value->as<T>();
+ }
return nullptr;
}
// This is a move operation and transfers ownership.
static QV4::Value *takeManagedValue(QJSValue *jsval)
{
- QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- if (!v.isManaged())
- return nullptr;
- if (QV4::Value *value = managedValue(v)) {
- setValue(jsval, QV4::Encode::undefined());
- return value;
+ if (tag(jsval->d) == Kind::QV4ValuePtr) {
+ if (QV4::Value *value = qv4ValuePtr(jsval->d)) {
+ jsval->d = encodeUndefined();
+ return value;
+ }
}
return nullptr;
}
static QV4::ReturnedValue asPrimitiveType(const QJSValue *jsval)
{
- const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- return v.isManaged() ? QV4::Encode::undefined() : v.asReturnedValue();
+ switch (tag(jsval->d)) {
+ case Kind::BoolValue:
+ return QV4::Encode(boolValue(jsval->d));
+ case Kind::IntValue:
+ return QV4::Encode(intValue(jsval->d));
+ case Kind::DoublePtr:
+ return QV4::Encode(*doublePtr(jsval->d));
+ case Kind::Null:
+ return QV4::Encode::null();
+ case Kind::Undefined:
+ case Kind::QV4ValuePtr:
+ case Kind::QStringPtr:
+ break;
+ }
+
+ return QV4::Encode::undefined();
}
// Beware: This only returns a non-null string if the QJSValue actually holds one.
// QV4::Strings are kept as managed values. Retrieve those with getValue().
static const QString *asQString(const QJSValue *jsval)
{
- const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- return v.isManaged() ? qstring(v) : nullptr;
+ if (tag(jsval->d) == Kind::QStringPtr) {
+ if (const QString *string = qStringPtr(jsval->d))
+ return string;
+ }
+ return nullptr;
}
static QV4::ReturnedValue asReturnedValue(const QJSValue *jsval)
{
- const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- if (!v.isManaged())
- return v.asReturnedValue();
-
- if (const QV4::Value *value = managedValue(v))
- return value->asReturnedValue();
+ switch (tag(jsval->d)) {
+ case Kind::BoolValue:
+ return QV4::Encode(boolValue(jsval->d));
+ case Kind::IntValue:
+ return QV4::Encode(intValue(jsval->d));
+ case Kind::DoublePtr:
+ return QV4::Encode(*doublePtr(jsval->d));
+ case Kind::Null:
+ return QV4::Encode::null();
+ case Kind::QV4ValuePtr:
+ return qv4ValuePtr(jsval->d)->asReturnedValue();
+ case Kind::Undefined:
+ case Kind::QStringPtr:
+ break;
+ }
return QV4::Encode::undefined();
}
@@ -146,21 +272,23 @@ public:
jsval->d = encode(std::move(s));
}
- static void setValue(QJSValue *jsval, const QV4::Value &v)
+ // Only use this with an existing persistent value.
+ // Ownership is transferred to the QJSValue.
+ static void adoptPersistentValue(QJSValue *jsval, QV4::Value *v)
{
- jsval->d = v.isManaged() ? encode(v) : v.asReturnedValue();
+ jsval->d = encodePointer(v, Kind::QV4ValuePtr);
}
- static void adoptValue(QJSValue *jsval, QV4::Value *v)
+ static void setValue(QJSValue *jsval, const QV4::Value &v)
{
- jsval->d = v->isManaged() ? encodeRawValue(quintptr(v)) : v->asReturnedValue();
+ jsval->d = encode(v);
}
// Moves any QString onto the V4 heap, changing the value to reflect that.
static void manageStringOnV4Heap(QV4::ExecutionEngine *e, QJSValue *jsval)
{
if (const QString *string = asQString(jsval)) {
- jsval->d = encode(e->newString(*string)->asReturnedValue());
+ jsval->d = encode(QV4::Value::fromHeapObject(e->newString(*string)));
delete string;
}
}
@@ -179,17 +307,15 @@ public:
qWarning("JSValue can't be reassigned to another engine.");
return QV4::Encode::undefined();
}
- return jsval.d;
+ return asPrimitiveType(&jsval);
}
static QV4::ExecutionEngine *engine(const QJSValue *jsval)
{
- const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- if (!v.isManaged())
- return nullptr;
-
- if (const QV4::Value *m = managedValue(v))
- return QV4::PersistentValueStorage::getEngine(m);
+ if (tag(jsval->d) == Kind::QV4ValuePtr) {
+ if (const QV4::Value *value = qv4ValuePtr(jsval->d))
+ return QV4::PersistentValueStorage::getEngine(value);
+ }
return nullptr;
}
@@ -202,19 +328,25 @@ public:
static void free(QJSValue *jsval)
{
- QV4::Value v = QV4::Value::fromReturnedValue(jsval->d);
- if (!v.isManaged())
+ switch (tag(jsval->d)) {
+ case Kind::Undefined:
+ case Kind::Null:
+ case Kind::IntValue:
+ case Kind::BoolValue:
return;
-
- if (const QString *m = qstring(v)) {
- delete m;
+ case Kind::DoublePtr:
+ delete doublePtr(jsval->d);
+ return;
+ case Kind::QStringPtr:
+ delete qStringPtr(jsval->d);
return;
+ case Kind::QV4ValuePtr:
+ break;
}
// We need a mutable value for free(). It needs to write to the actual memory.
- Q_ASSERT(!(quintptr(v.m()) & IsString));
- QV4::Value *m = reinterpret_cast<QV4::Value *>(v.m());
- Q_ASSERT(m); // Otherwise it would have been undefined, that is !v.isManaged() above.
+ QV4::Value *m = qv4ValuePtr(jsval->d);
+ Q_ASSERT(m); // Otherwise it would have been undefined above.
if (QV4::ExecutionEngine *e = QV4::PersistentValueStorage::getEngine(m)) {
if (QJSEngine *jsEngine = e->jsEngine()) {
if (jsEngine->thread() != QThread::currentThread()) {