From d71cd95ed255ff64e736a31d1f1480bfc8ecd5ab Mon Sep 17 00:00:00 2001 From: Littlegnal <8847263+littleGnAl@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:06:47 +0800 Subject: [PATCH] Fix can not parse 64-bits int from JSON (#1758) The native side will return a 64-bits int ptr value, most of it is `view_t`, `void *`, which will cause the dart `jsonDecode` error, so we passed the int ptr value through the string, and parse it to the int value. This change is not yet supported on iris 4.3.x, so we do not apply any code changes at this time. --- lib/src/impl/json_converters.dart | 38 +++++++++++++++ test/json_converters_test.dart | 81 +++++++++++++++++++++++++++++++ tool/terra/renderers/utils.ts | 66 ++++++++++++++++++++++++- 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 test/json_converters_test.dart diff --git a/lib/src/impl/json_converters.dart b/lib/src/impl/json_converters.dart index d4322c743..db1943a2a 100644 --- a/lib/src/impl/json_converters.dart +++ b/lib/src/impl/json_converters.dart @@ -51,3 +51,41 @@ class _VideoFrameMetaInfoInternalJson { /// @nodoc Map toJson() => _$VideoFrameMetaInfoInternalJsonToJson(this); } + +int _intPtrStr2Int(String value) { + // In 64-bits system, the c++ int ptr value (unsigned long 64) can be 2^64 - 1, + // which may greater than the dart int max value (2^63 - 1), so we can not decode + // the json with big int c++ int ptr value and parse it directly. + // + // After dart sdk 2.0 support parse hexadecimal in unsigned int64 range. + // https://github.com/dart-lang/language/blob/ee1135e0c22391cee17bf3ee262d6a04582d25de/archive/newsletter/20170929.md#semantics + // + // So we retrive the c++ int ptr value from the json string directly, and + // parse an int from hexadecimal here. + BigInt valueBI = BigInt.parse(value); + return int.tryParse('0x${valueBI.toRadixString(16)}') ?? 0; +} + +/// Parse a c++ int ptr value from the json key `_str` +Object? readIntPtr(Map json, String key) { + final newKey = '${key}_str'; + if (json.containsKey(newKey)) { + final value = json[newKey]; + assert(value is String); + return _intPtrStr2Int(value); + } + + return json[key]; +} + +/// Same as `readIntPtr`, but for list of int ptr. +Object? readIntPtrList(Map json, String key) { + final newKey = '${key}_str'; + if (json.containsKey(newKey)) { + final value = json[newKey]; + assert(value is List); + return List.from(value.map((e) => _intPtrStr2Int(e))); + } + + return json[key]; +} diff --git a/test/json_converters_test.dart b/test/json_converters_test.dart new file mode 100644 index 000000000..150ac51ee --- /dev/null +++ b/test/json_converters_test.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; + +import 'package:agora_rtc_engine/src/impl/json_converters.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('readIntPtr', () { + test('can parse int ptr from key pattern _str', () async { + const json = ''' +{ + "key": 18446744073709551615, + "key_str": "18446744073709551615" +} +'''; + final res = readIntPtr(jsonDecode(json), 'key'); + // 18446744073709551615 is -1 in c++ + expect(res, -1); + }); + + test('can parse int ptr if no key pattern _str', () async { + const json = ''' +{ + "key": 18446744073709551615 +} +'''; + final res = readIntPtr(jsonDecode(json), 'key'); + // 18446744073709551615 become 18446744073709552000.0 after `jsonDecode` + expect(res, 18446744073709552000.0); + }); + + test( + 'throw assertion error if value of key pattern _str is not type of String', + () async { + const json = ''' +{ + "key": 18446744073709551615, + "key_str": 18446744073709551615 +} +'''; + expect(() => readIntPtr(jsonDecode(json), 'key'), throwsAssertionError); + }); + }); + + group('readIntPtrList', () { + test('can parse int ptr list from key pattern _str', () async { + const json = ''' +{ + "key": [18446744073709551615, 18446744073709551615], + "key_str": ["18446744073709551615", "18446744073709551615"] +} +'''; + final res = readIntPtrList(jsonDecode(json), 'key'); + // 18446744073709551615 is -1 in c++ + expect(res, const [-1, -1]); + }); + + test('can parse int ptr list if no key pattern _str', () async { + const json = ''' +{ + "key": [18446744073709551615, 18446744073709551615] +} +'''; + final res = readIntPtrList(jsonDecode(json), 'key'); + // 18446744073709551615 become 18446744073709552000.0 after `jsonDecode` + expect(res, [18446744073709552000.0, 18446744073709552000.0]); + }); + + test( + 'throw assertion error if value of key pattern _str is not type of List', + () async { + const json = ''' +{ + "key": [18446744073709551615, 18446744073709551615], + "key_str": 18446744073709551615 +} +'''; + expect( + () => readIntPtrList(jsonDecode(json), 'key'), throwsAssertionError); + }); + }); +} diff --git a/tool/terra/renderers/utils.ts b/tool/terra/renderers/utils.ts index 6b31d3ed1..12f930ad9 100644 --- a/tool/terra/renderers/utils.ts +++ b/tool/terra/renderers/utils.ts @@ -5,6 +5,7 @@ import { MemberFunction, MemberVariable, SimpleType, + SimpleTypeKind, Variable, } from "@agoraio-extensions/cxx-parser"; import { ParseResult } from "@agoraio-extensions/terra-core"; @@ -155,6 +156,10 @@ export function renderJsonSerializable( ${memberVariables .map((it) => { let isIgnoreJson = isNeedIgnoreJsonInJsonObject(parseResult, it.type); + let isNeedReadValueWithReadIntPtr = + !isIgnoreJson && isUIntPtr(parseResult, it.type); + let isNeedReadValueWithReadIntPtrList = + !isIgnoreJson && isUIntPtrList(parseResult, it.type); let actualNode = parseResult.resolveNodeByType(it.type); // Campatible with the old code. isIgnoreJson = @@ -166,9 +171,20 @@ export function renderJsonSerializable( actualNode.__TYPE == CXXTYPE.Clazz && dartName(actualNode) == "VideoFrameMetaInfo"; let nullableSurffix = forceExplicitNullableType ? "?" : ""; + let jsonKeyAnnotations = [ + `name: '${it.name}'`, + ...(isIgnoreJson ? ["ignore: true"] : []), + ...(isNeedReadValueWithReadIntPtr && + !isNeedReadValueWithReadIntPtrList + ? ["readValue: readIntPtr"] + : []), + ...(isNeedReadValueWithReadIntPtr && isNeedReadValueWithReadIntPtrList + ? ["readValue: readIntPtrList"] + : []), + ]; return ` ${isClazz ? `@${dartName(actualNode)}Converter()` : ""} - @JsonKey(name: '${it.name}'${isIgnoreJson ? ", ignore: true" : ""}) + @JsonKey(${jsonKeyAnnotations.join(",")}) final ${dartName(it.type)}${nullableSurffix} ${dartName(it)}; `.trim(); }) @@ -219,3 +235,51 @@ export function variableToMemberVariable(it: Variable): MemberVariable { user_data: it.user_data, } as MemberVariable; } + +const stdIntTypes = [ + "int8_t", + "int16_t", + "int32_t", + "int64_t", + "uint8_t", + "uint16_t", + "uint32_t", + "uint64_t", + "size_t", +]; + +// TODO(littlegnal): Move to cxx-parser +export function isStdIntType(typeName: string): boolean { + return stdIntTypes.includes(typeName); +} + +function isBufferPtr(type: SimpleType): boolean { + return ( + type.kind == SimpleTypeKind.pointer_t && + (isStdIntType(type.name) || + type.source.includes("unsigned char") || + type.name.toLowerCase().includes("buffer") || + type.source.includes("void")) + ); +} + +export function isUIntPtr(parseResult: ParseResult, type: SimpleType): boolean { + let isUIntPtr = isBufferPtr(type); + if (!isUIntPtr) { + let actualNode = parseResult.resolveNodeByType(type); + if (actualNode.__TYPE == CXXTYPE.TypeAlias) { + isUIntPtr = isBufferPtr(actualNode.asTypeAlias().underlyingType); + } + } + + return isUIntPtr; +} + +export function isUIntPtrList( + parseResult: ParseResult, + type: SimpleType +): boolean { + let isPtrList = isUIntPtr(parseResult, type); + isPtrList = isPtrList && type.kind == SimpleTypeKind.array_t; + return isPtrList; +}