Skip to content

Commit

Permalink
Fix can not parse 64-bits int from JSON (#1758)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
littleGnAl authored Jun 7, 2024
1 parent d7415ab commit d71cd95
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 1 deletion.
38 changes: 38 additions & 0 deletions lib/src/impl/json_converters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,41 @@ class _VideoFrameMetaInfoInternalJson {
/// @nodoc
Map<String, dynamic> 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 `<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];
}
81 changes: 81 additions & 0 deletions test/json_converters_test.dart
Original file line number Diff line number Diff line change
@@ -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 <key>_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 <key>_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 <key>_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 <key>_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 <key>_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 <key>_str is not type of List',
() async {
const json = '''
{
"key": [18446744073709551615, 18446744073709551615],
"key_str": 18446744073709551615
}
''';
expect(
() => readIntPtrList(jsonDecode(json), 'key'), throwsAssertionError);
});
});
}
66 changes: 65 additions & 1 deletion tool/terra/renderers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MemberFunction,
MemberVariable,
SimpleType,
SimpleTypeKind,
Variable,
} from "@agoraio-extensions/cxx-parser";
import { ParseResult } from "@agoraio-extensions/terra-core";
Expand Down Expand Up @@ -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 =
Expand All @@ -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();
})
Expand Down Expand Up @@ -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;
}

0 comments on commit d71cd95

Please sign in to comment.