Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Fix JSON.{from,to}_native() issues #99765

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,364 changes: 812 additions & 552 deletions core/io/json.cpp

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions core/io/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class JSON : public Resource {
static Error _parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
static Error _parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line);

static Variant _from_native(const Variant &p_variant, bool p_full_objects, int p_depth);
static Variant _to_native(const Variant &p_json, bool p_allow_objects, int p_depth);

protected:
static void _bind_methods();

Expand All @@ -90,13 +93,18 @@ class JSON : public Resource {
static String stringify(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true, bool p_full_precision = false);
static Variant parse_string(const String &p_json_string);

inline Variant get_data() const { return data; }
_FORCE_INLINE_ static Variant from_native(const Variant &p_variant, bool p_full_objects = false) {
return _from_native(p_variant, p_full_objects, 0);
}
_FORCE_INLINE_ static Variant to_native(const Variant &p_json, bool p_allow_objects = false) {
return _to_native(p_json, p_allow_objects, 0);
}

void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; }
_FORCE_INLINE_ Variant get_data() const { return data; }

static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false);
static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false);
_FORCE_INLINE_ int get_error_line() const { return err_line; }
_FORCE_INLINE_ String get_error_message() const { return err_str; }
};

class ResourceFormatLoaderJSON : public ResourceFormatLoader {
Expand Down
66 changes: 14 additions & 52 deletions core/io/marshalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/variant/container_type_validate.h"

#include <limits.h>
#include <stdio.h>
Expand Down Expand Up @@ -84,12 +85,6 @@ enum ContainerTypeKind {
CONTAINER_TYPE_KIND_SCRIPT = 0b11,
};

struct ContainerType {
Variant::Type builtin_type = Variant::NIL;
StringName class_name;
Ref<Script> script;
};

#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \
((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT))

Expand Down Expand Up @@ -844,7 +839,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int

Dictionary dict;
if (key_type.builtin_type != Variant::NIL || value_type.builtin_type != Variant::NIL) {
dict.set_typed(key_type.builtin_type, key_type.class_name, key_type.script, value_type.builtin_type, value_type.class_name, value_type.script);
dict.set_typed(key_type, value_type);
}

for (int i = 0; i < count; i++) {
Expand Down Expand Up @@ -901,7 +896,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int

Array array;
if (type.builtin_type != Variant::NIL) {
array.set_typed(type.builtin_type, type.class_name, type.script);
array.set_typed(type);
}

for (int i = 0; i < count; i++) {
Expand Down Expand Up @@ -1402,31 +1397,13 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
} break;
case Variant::DICTIONARY: {
Dictionary dict = p_variant;

ContainerType key_type;
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
key_type.class_name = dict.get_typed_key_class_name();
key_type.script = dict.get_typed_key_script();

_encode_container_type_header(key_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);

ContainerType value_type;
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
value_type.class_name = dict.get_typed_value_class_name();
value_type.script = dict.get_typed_value_script();

_encode_container_type_header(value_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
const Dictionary dict = p_variant;
_encode_container_type_header(dict.get_key_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
_encode_container_type_header(dict.get_value_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
} break;
case Variant::ARRAY: {
Array array = p_variant;

ContainerType type;
type.builtin_type = (Variant::Type)array.get_typed_builtin();
type.class_name = array.get_typed_class_name();
type.script = array.get_typed_script();

_encode_container_type_header(type, header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
const Array array = p_variant;
_encode_container_type_header(array.get_element_type(), header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
} break;
#ifdef REAL_T_IS_DOUBLE
case Variant::VECTOR2:
Expand Down Expand Up @@ -1850,27 +1827,17 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 8;
} break;
case Variant::DICTIONARY: {
Dictionary dict = p_variant;
const Dictionary dict = p_variant;

{
ContainerType key_type;
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
key_type.class_name = dict.get_typed_key_class_name();
key_type.script = dict.get_typed_key_script();

Error err = _encode_container_type(key_type, buf, r_len, p_full_objects);
Error err = _encode_container_type(dict.get_key_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
}

{
ContainerType value_type;
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
value_type.class_name = dict.get_typed_value_class_name();
value_type.script = dict.get_typed_value_script();

Error err = _encode_container_type(value_type, buf, r_len, p_full_objects);
Error err = _encode_container_type(dict.get_value_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
Expand All @@ -1894,7 +1861,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
if (buf) {
buf += len;
}
Variant *value = dict.getptr(key);
const Variant *value = dict.getptr(key);
ERR_FAIL_NULL_V(value, ERR_BUG);
err = encode_variant(*value, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
Expand All @@ -1907,15 +1874,10 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo

} break;
case Variant::ARRAY: {
Array array = p_variant;
const Array array = p_variant;

{
ContainerType type;
type.builtin_type = (Variant::Type)array.get_typed_builtin();
type.class_name = array.get_typed_class_name();
type.script = array.get_typed_script();

Error err = _encode_container_type(type, buf, r_len, p_full_objects);
Error err = _encode_container_type(array.get_element_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
Expand Down
7 changes: 4 additions & 3 deletions core/math/expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,10 +473,11 @@ Error Expression::_get_token(Token &r_token) {
} else if (id == "self") {
r_token.type = TK_SELF;
} else {
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (id == Variant::get_type_name(Variant::Type(i))) {
{
const Variant::Type type = Variant::get_type_by_name(id);
if (type < Variant::VARIANT_MAX) {
r_token.type = TK_BASIC_TYPE;
r_token.value = i;
r_token.value = type;
return OK;
}
}
Expand Down
15 changes: 13 additions & 2 deletions core/variant/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
#include "core/variant/dictionary.h"
#include "core/variant/variant.h"

class ArrayPrivate {
public:
struct ArrayPrivate {
SafeRefCount refcount;
Vector<Variant> array;
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
Expand Down Expand Up @@ -843,6 +842,10 @@ Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_nam
assign(p_from);
}

void Array::set_typed(const ContainerType &p_element_type) {
set_typed(p_element_type.builtin_type, p_element_type.class_name, p_element_type.script);
}

void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty.");
Expand Down Expand Up @@ -870,6 +873,14 @@ bool Array::is_same_instance(const Array &p_other) const {
return _p == p_other._p;
}

ContainerType Array::get_element_type() const {
ContainerType type;
type.builtin_type = _p->typed.type;
type.class_name = _p->typed.class_name;
type.script = _p->typed.script;
return type;
}

uint32_t Array::get_typed_builtin() const {
return _p->typed.type;
}
Expand Down
12 changes: 9 additions & 3 deletions core/variant/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@

#include <climits>

class Variant;
class ArrayPrivate;
class Callable;
class Object;
class StringName;
class Callable;
class Variant;

struct ArrayPrivate;
struct ContainerType;

class Array {
mutable ArrayPrivate *_p;
Expand Down Expand Up @@ -185,10 +187,14 @@ class Array {

const void *id() const;

void set_typed(const ContainerType &p_element_type);
void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script);

bool is_typed() const;
bool is_same_typed(const Array &p_other) const;
bool is_same_instance(const Array &p_other) const;

ContainerType get_element_type() const;
uint32_t get_typed_builtin() const;
StringName get_typed_class_name() const;
Variant get_typed_script() const;
Expand Down
6 changes: 6 additions & 0 deletions core/variant/container_type_validate.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
#include "core/object/script_language.h"
#include "core/variant/variant.h"

struct ContainerType {
Variant::Type builtin_type = Variant::NIL;
StringName class_name;
Ref<Script> script;
};

struct ContainerTypeValidate {
Variant::Type type = Variant::NIL;
StringName class_name;
Expand Down
20 changes: 20 additions & 0 deletions core/variant/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,10 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
return n;
}

void Dictionary::set_typed(const ContainerType &p_key_type, const ContainerType &p_value_type) {
set_typed(p_key_type.builtin_type, p_key_type.class_name, p_key_type.script, p_value_type.builtin_type, p_value_type.class_name, p_key_type.script);
}

void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty.");
Expand Down Expand Up @@ -641,6 +645,22 @@ bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
return _p->typed_value == p_other._p->typed_value;
}

ContainerType Dictionary::get_key_type() const {
ContainerType type;
type.builtin_type = _p->typed_key.type;
type.class_name = _p->typed_key.class_name;
type.script = _p->typed_key.script;
return type;
}

ContainerType Dictionary::get_value_type() const {
ContainerType type;
type.builtin_type = _p->typed_value.type;
type.class_name = _p->typed_value.class_name;
type.script = _p->typed_value.script;
return type;
}

uint32_t Dictionary::get_typed_key_builtin() const {
return _p->typed_key.type;
}
Expand Down
6 changes: 6 additions & 0 deletions core/variant/dictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

class Variant;

struct ContainerType;
struct DictionaryPrivate;

class Dictionary {
Expand Down Expand Up @@ -91,13 +92,18 @@ class Dictionary {
Dictionary duplicate(bool p_deep = false) const;
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;

void set_typed(const ContainerType &p_key_type, const ContainerType &p_value_type);
void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);

bool is_typed() const;
bool is_typed_key() const;
bool is_typed_value() const;
bool is_same_typed(const Dictionary &p_other) const;
bool is_same_typed_key(const Dictionary &p_other) const;
bool is_same_typed_value(const Dictionary &p_other) const;

ContainerType get_key_type() const;
ContainerType get_value_type() const;
uint32_t get_typed_key_builtin() const;
uint32_t get_typed_value_builtin() const;
StringName get_typed_key_class_name() const;
Expand Down
12 changes: 12 additions & 0 deletions core/variant/variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ String Variant::get_type_name(Variant::Type p_type) {
return "";
}

Variant::Type Variant::get_type_by_name(const String &p_type_name) {
static HashMap<String, Type> type_names;
if (unlikely(type_names.is_empty())) {
for (int i = 0; i < VARIANT_MAX; i++) {
type_names[get_type_name((Type)i)] = (Type)i;
}
}

const Type *ptr = type_names.getptr(p_type_name);
return (ptr == nullptr) ? VARIANT_MAX : *ptr;
}

bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
if (p_type_from == p_type_to) {
return true;
Expand Down
1 change: 1 addition & 0 deletions core/variant/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ class Variant {
return type;
}
static String get_type_name(Variant::Type p_type);
static Variant::Type get_type_by_name(const String &p_type_name);
static bool can_convert(Type p_type_from, Type p_type_to);
static bool can_convert_strict(Type p_type_from, Type p_type_to);
static bool is_type_shared(Variant::Type p_type);
Expand Down
24 changes: 16 additions & 8 deletions doc/classes/JSON.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
<method name="from_native" qualifiers="static">
<return type="Variant" />
<param index="0" name="variant" type="Variant" />
<param index="1" name="allow_classes" type="bool" default="false" />
<param index="2" name="allow_scripts" type="bool" default="false" />
<param index="1" name="full_objects" type="bool" default="false" />
<description>
Converts a native engine type to a JSON-compliant dictionary.
By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
Converts a native engine type to a JSON-compliant value.
By default, objects are ignored for security reasons, unless [param full_objects] is [code]true[/code].
You can convert a native value to a JSON string like this:
[codeblock]
func encode_data(value, full_objects = false):
return JSON.stringify(JSON.from_native(value, full_objects))
[/codeblock]
</description>
</method>
<method name="get_error_line" qualifiers="const">
Expand Down Expand Up @@ -136,11 +140,15 @@
<method name="to_native" qualifiers="static">
<return type="Variant" />
<param index="0" name="json" type="Variant" />
<param index="1" name="allow_classes" type="bool" default="false" />
<param index="2" name="allow_scripts" type="bool" default="false" />
<param index="1" name="allow_objects" type="bool" default="false" />
<description>
Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types.
By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
Converts a JSON-compliant value that was created with [method from_native] back to native engine types.
By default, objects are ignored for security reasons, unless [param allow_objects] is [code]true[/code].
You can convert a JSON string back to a native value like this:
[codeblock]
func decode_data(string, allow_objects = false):
return JSON.to_native(JSON.parse_string(string), allow_objects)
[/codeblock]
</description>
</method>
</methods>
Expand Down
Loading