blob: 92de910bd0c086975338e8569f6475eaea361383 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//
// wsonjsc.cpp
// JavaScriptCore
//
// Created by furture on 2017/8/30.
//
//
#include "android/jsengine/wson/wson_jsc.h"
#include "include/JavaScriptCore/runtime/ObjectConstructor.h"
#include "include/JavaScriptCore/runtime/JSONObject.h"
#include "include/JavaScriptCore/runtime/JSCJSValueInlines.h"
#include <wtf/Vector.h>
#include <wtf/HashMap.h>
#ifdef __ANDROID__
//#define WSON_JSC_DEBUG true;
#include <android/log.h>
#include <wtf/unicode/WTFUTF8.h>
#define TAG "weex"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#else
#define LOGE(...) printf(__VA_ARGS__)
#include <wtf/unicode/UTF8.h>
#endif
using namespace JSC;
/**
* max deep, like JSONObject.cpp's maximumFilterRecursion default 40000
*/
#define WSON_MAX_DEEP 40000
#define WSON_SYSTEM_IDENTIFIER_CACHE_COUNT (1024*4)
#define WSON_LOCAL_IDENTIFIER_CACHE_COUNT 32
namespace wson {
struct IdentifierCache{
Identifier identifer = Identifier::EmptyIdentifier;
size_t length = 0;
uint32_t key = 0;
const UChar* utf16 = NULL;
};
static IdentifierCache* systemIdentifyCache = nullptr;
static VM* systemIdentifyCacheVM = nullptr;
void wson_push_js_value(ExecState* exec, JSValue val, wson_buffer* buffer, Vector<JSObject*, 16>& objectStack);
JSValue wson_to_js_value(ExecState* state, wson_buffer* buffer, IdentifierCache* localIdentifiers, const int& localCount);
inline void wson_push_js_string(ExecState* exec, JSValue val, wson_buffer* buffer);
inline void wson_push_js_identifier(Identifier val, wson_buffer* buffer);
JSValue call_object_js_value_to_json(ExecState* exec, JSValue val, VM& vm, Identifier* identifier);
JSValue call_object_js_value_to_json(ExecState* exec, JSValue val, VM& vm, uint32_t index);
wson_buffer* toWson(ExecState* exec, JSValue val){
#ifdef WSON_JSC_DEBUG
LOGE("weex wson pre %s", JSONStringify(exec, val, 0).utf8().data());
#endif
VM& vm = exec->vm();
LocalScope localScope(exec->vm());
Identifier emptyIdentifier = vm.propertyNames->emptyIdentifier;
if(val.isObject()){
val = call_object_js_value_to_json(exec, val, vm, &emptyIdentifier);
}
wson_buffer* buffer = wson_buffer_new();
Vector<JSObject*, 16> objectStack;
wson_push_js_value(exec, val, buffer, objectStack);
#ifdef WSON_JSC_DEBUG
int position = buffer->position;
buffer->position = 0;
buffer->length = position;
JSValue back = toJSValue(exec, buffer);
LOGE("weex wson back %s", JSONStringify(exec, back, 0).utf8().data());
buffer->position = position;
#endif
return buffer;
}
/**
* if has system cache, local cache size 64, if not, local cache size 128
*/
JSValue toJSValue(ExecState* exec, wson_buffer* buffer){
VM& vm =exec->vm();
LocalScope scope(vm);
if(systemIdentifyCacheVM && systemIdentifyCacheVM == &vm){
return wson_to_js_value(exec, buffer, systemIdentifyCache, WSON_SYSTEM_IDENTIFIER_CACHE_COUNT);
}
if(buffer->length < 256){
IdentifierCache localIdentifiers[WSON_LOCAL_IDENTIFIER_CACHE_COUNT];
return wson_to_js_value(exec, buffer, localIdentifiers, WSON_LOCAL_IDENTIFIER_CACHE_COUNT);
}
if(buffer->length < 1024*2){
IdentifierCache localIdentifiers[WSON_LOCAL_IDENTIFIER_CACHE_COUNT*2];
return wson_to_js_value(exec, buffer, localIdentifiers, WSON_LOCAL_IDENTIFIER_CACHE_COUNT*2);
}
IdentifierCache localIdentifiers[WSON_LOCAL_IDENTIFIER_CACHE_COUNT*4];
JSValue value = wson_to_js_value(exec, buffer, localIdentifiers, WSON_LOCAL_IDENTIFIER_CACHE_COUNT*4);
return value;
}
JSValue toJSValue(ExecState* state, void* data, int length){
wson_buffer* buffer = wson_buffer_from(data, length);
#ifdef WSON_JSC_DEBUG
LOGE("weex wson toJSValue length %d %d", buffer->position, buffer->length);
#endif
JSValue ret = toJSValue(state, buffer);
#ifdef WSON_JSC_DEBUG
LOGE("weex wson toJSValue %s", JSONStringify(state, ret, 0).utf8().data());
wson_buffer* backBuffer = toWson(state, ret);
LOGE("weex wson toJSValue backBuffer length %d", backBuffer->position);
wson_buffer_free(backBuffer);
#endif
buffer->data = nullptr;
wson_buffer_free(buffer);
return ret;
}
void init(VM* vm){
if(systemIdentifyCache){
destory();
}
if(systemIdentifyCacheVM){
systemIdentifyCacheVM = nullptr;
}
systemIdentifyCache = new IdentifierCache[WSON_SYSTEM_IDENTIFIER_CACHE_COUNT];
for(int i=0; i<WSON_SYSTEM_IDENTIFIER_CACHE_COUNT; i++){
systemIdentifyCache[i].identifer = vm->propertyNames->nullIdentifier;
systemIdentifyCache[i].length = 0;
systemIdentifyCache[i].utf16 = NULL;
systemIdentifyCache[i].key = 0;
}
systemIdentifyCacheVM = vm;
}
void destory(){
if(systemIdentifyCache){
for(int i=0; i<WSON_SYSTEM_IDENTIFIER_CACHE_COUNT; i++){
if(systemIdentifyCache[i].length > 0){
if(systemIdentifyCache[i].utf16){
free((void*)systemIdentifyCache[i].utf16);
systemIdentifyCache[i].utf16 = nullptr;
}
systemIdentifyCache[i].key = 0;
systemIdentifyCache[i].length = 0;
}
}
delete[] systemIdentifyCache;
systemIdentifyCache = nullptr;
}
if(systemIdentifyCacheVM){
systemIdentifyCacheVM = nullptr;
}
}
/**
* check is circle reference and max deep
*/
inline bool check_js_deep_and_circle_reference(JSObject* object, Vector<JSObject*, 16>& objectStack){
for (unsigned i = 0; i < objectStack.size(); i++) {
if (objectStack.at(i) == object) {
return true;
}
}
if(objectStack.size() > WSON_MAX_DEEP){
return true;
}
return false;
}
/**
* djb2-hash-function
*/
inline uint32_t hash(const UChar* utf16, const int length){
uint32_t hash = 5381;
for(int i=0;i<length; i++){
hash = ((hash << 5) + hash) + utf16[i];
}
return hash;
}
inline uint32_t hash2(const UChar* utf16, const int length){
uint32_t hash = 5381;
hash = ((hash << 5) + hash) + utf16[0];
if(length > 1){
hash = ((hash << 5) + hash) + utf16[length-1];
}
return hash;
}
/**
* most of json identifer is repeat, cache can improve performance fixme
* cache global, improve performance
*/
inline Identifier makeIdentifer(VM* vm, IdentifierCache* localIdentifiers, const int& localCount, const UChar* utf16, const size_t length){
if(length <= 0){
return vm->propertyNames->emptyIdentifier;
}
if (utf16[0] <= 0 || utf16[0] >= 127 || length > 32){//only cache short identifier
return Identifier::fromString(vm, utf16, length);
}
IdentifierCache cache;
uint32_t key= hash(utf16, length);
uint32_t localIndex = (localCount - 1) & key;
cache = localIdentifiers[localIndex];
if(cache.length == length
&& cache.key == key
&& memcmp(
(void*)cache.utf16,
(void*)utf16, length*sizeof(UChar)) == 0
&& !cache.identifer.isNull()){
return cache.identifer;
}
UChar* destination = static_cast<UChar*>(malloc(length*sizeof(UChar) + sizeof(UChar)));
memset((void*)destination, 0, length*sizeof(UChar) + sizeof(UChar));
memcpy((void*)destination, (void*)utf16, length*sizeof(UChar));
Identifier identifier = Identifier::fromString(vm, destination, length);
cache.identifer = identifier;
cache.length = length;
cache.key = key;
cache.utf16 = destination;
localIdentifiers[localIndex] = cache;
return identifier;
}
JSValue wson_to_js_value(ExecState* exec, wson_buffer* buffer, IdentifierCache* localIdentifiers, const int& localCount){
uint8_t type = wson_next_type(buffer);
switch (type) {
case WSON_UINT8_STRING_TYPE: {
int size = wson_next_uint(buffer);
uint8_t *utf8 = wson_next_bts(buffer, size);
String s = String::fromUTF8(reinterpret_cast<char *>(utf8), size);
return jsString(exec, s);
}
case WSON_STRING_TYPE:
case WSON_NUMBER_BIG_INT_TYPE:
case WSON_NUMBER_BIG_DECIMAL_TYPE:{
uint32_t length = wson_next_uint(buffer);
UChar* destination;
String s = String::createUninitialized(length/sizeof(UChar), destination);
void* src = wson_next_bts(buffer, length);
memcpy(destination, src, length);
return jsString(exec, s);
}
break;
case WSON_ARRAY_TYPE:{
uint32_t length = wson_next_uint(buffer);
JSArray* array = constructEmptyArray(exec, 0);
for(uint32_t i=0; i<length; i++){
if(wson_has_next(buffer)){
array->putDirectIndex(exec, i, wson_to_js_value(exec, buffer, localIdentifiers, localCount));
}else{
break;
}
}
return array;
}
break;
case WSON_MAP_TYPE:{
uint32_t length = wson_next_uint(buffer);
JSObject* object = constructEmptyObject(exec);
VM& vm = exec->vm();
for(uint32_t i=0; i<length; i++){
if(wson_has_next(buffer)){
int propertyLength = wson_next_uint(buffer);
const UChar* data = (const UChar*)wson_next_bts(buffer, propertyLength);
Identifier identifer = makeIdentifer(&vm, localIdentifiers, localCount, data, propertyLength/sizeof(UChar));
PropertyName name = identifer;
if (std::optional<uint32_t> index = parseIndex(name)){
object->putDirectIndex(exec, index.value(), wson_to_js_value(exec, buffer, localIdentifiers, localCount));
}else{
object->putDirect(vm, name, wson_to_js_value(exec, buffer, localIdentifiers, localCount));
}
}else{
break;
}
}
return object;
}
break;
case WSON_NUMBER_INT_TYPE:{
int32_t num = wson_next_int(buffer);
return jsNumber(num);
}
break;
case WSON_BOOLEAN_TYPE_TRUE:{
return jsBoolean(true);
}
break;
case WSON_BOOLEAN_TYPE_FALSE:{
return jsBoolean(false);
}
break;
case WSON_NUMBER_DOUBLE_TYPE:{
double num = wson_next_double(buffer);
return jsNumber(num);
}
break;
case WSON_NUMBER_FLOAT_TYPE:{
float num = wson_next_float(buffer);
return jsNumber(num);
}
break;
case WSON_NUMBER_LONG_TYPE:{
int64_t num = wson_next_long(buffer);
return jsNumber(num);
}
break;
case WSON_NULL_TYPE:{
return jsNull();
}
break;
default:
#ifdef __ANDROID__
LOGE("weex weex wson err wson_to_js_value unhandled type %d buffer position %d length %d", type, buffer->position, buffer->length);
#endif
break;
}
return jsNull();
}
JSValue call_object_js_value_to_json(ExecState* exec, JSValue val, VM& vm, Identifier* identifier){
JSObject* object = asObject(val);
PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
bool hasProperty = object->getPropertySlot(exec, vm.propertyNames->toJSON, slot);
if (hasProperty){
JSValue toJSONFunction = slot.getValue(exec, vm.propertyNames->toJSON);
CallType callType;
CallData callData;
if (toJSONFunction.isCallable(callType, callData)){
MarkedArgumentBuffer args;
args.append(jsString(exec, identifier->string()));
return call(exec, asObject(toJSONFunction), callType, callData, val, args);
}
}
return val;
}
JSValue call_object_js_value_to_json(ExecState* exec, JSValue val, VM& vm, uint32_t index){
JSObject* object = asObject(val);
PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
bool hasProperty = object->getPropertySlot(exec, vm.propertyNames->toJSON, slot);
if (hasProperty){
JSValue toJSONFunction = slot.getValue(exec, vm.propertyNames->toJSON);
CallType callType;
CallData callData;
if (toJSONFunction.isCallable(callType, callData)){
MarkedArgumentBuffer args;
if(index <= 9){
args.append(vm.smallStrings.singleCharacterString(index + '0'));
}else{
args.append(jsNontrivialString(&vm, vm.numericStrings.add(index)));
}
return call(exec, asObject(toJSONFunction), callType, callData, val, args);
}
}
return val;
}
void wson_push_js_value(ExecState* exec, JSValue val, wson_buffer* buffer, Vector<JSObject*, 16>& objectStack){
// check json function
if(val.isNull() || val.isUndefined() || val.isEmpty()){
wson_push_type_null(buffer);
return;
}
if(val.isString()){
wson_push_js_string(exec, val, buffer);
return;
}
if(val.isNumber()){
if(val.isInt32()){
wson_push_type_int(buffer, val.asInt32());
return;
}
if(val.isAnyInt()){
int64_t int64Number = val.asAnyInt();
wson_push_type_long(buffer, int64Number);
return;
}
if(val.isDouble()){
double number = val.asDouble();
wson_push_type_double(buffer, number);
return;
}
wson_push_js_string(exec, val, buffer);
return;
}
if(isJSArray(val)){
JSArray* array = asArray(val);
if(check_js_deep_and_circle_reference(array, objectStack)){
wson_push_type_null(buffer);
return;
}
VM& vm = exec->vm();
uint32_t length = array->length();
wson_push_type_array(buffer, length);
objectStack.append(array);
for(uint32_t index=0; index<length; index++){
JSValue ele = array->getIndex(exec, index);
if(ele.isObject()){
ele = call_object_js_value_to_json(exec, ele, vm, index);
}
wson_push_js_value(exec, ele, buffer, objectStack);
}
objectStack.removeLast();
return;
}
if(val.isObject() && !val.isFunction()){
JSObject* object = asObject(val);
VM& vm = exec->vm();
if (object->inherits(vm, StringObject::info())){
wson_push_js_string(exec, object->toString(exec), buffer);
return;
}
if (object->inherits(vm, NumberObject::info())){
JSValue number = jsNumber(object->toNumber(exec));
if(number.isInt32()){
wson_push_type_int(buffer, number.asInt32());
return;
}
if(number.isAnyInt()){
int64_t int64Number = number.asAnyInt();
wson_push_type_long(buffer, int64Number);
return;
}
if(val.isDouble()){
double d = val.asDouble();
wson_push_type_double(buffer, d);
return;
}
wson_push_js_string(exec, number, buffer);
return;
}
if (object->inherits(vm, BooleanObject::info())){
JSValue boolVal = object->toPrimitive(exec);
if(boolVal.isTrue()){
wson_push_type_boolean(buffer, 1);
}else{
wson_push_type_boolean(buffer, 0);
}
return;
}
if(check_js_deep_and_circle_reference(object, objectStack)){
wson_push_type_null(buffer);
return;
}
#ifdef __ANDROID__
PropertyNameArray objectPropertyNames(exec, PropertyNameMode::Strings);
#else
PropertyNameArray objectPropertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
#endif
const MethodTable* methodTable = object->methodTable();
methodTable->getOwnPropertyNames(object, exec, objectPropertyNames, EnumerationMode());
PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
uint32_t size = objectPropertyNames.size();
/**map should skip null or function */
/** first check skip null or function,calc null or function */
uint32_t undefinedOrFunctionSize = 0;
for(uint32_t i=0; i<size; i++){
Identifier& propertyName = objectPropertyNames[i];
if(methodTable->getOwnPropertySlot(object, exec, propertyName, slot)){
JSValue propertyValue = slot.getValue(exec, propertyName);
if(propertyValue.isUndefined() || propertyValue.isFunction()){
undefinedOrFunctionSize++;
}
}else{
undefinedOrFunctionSize++;
}
}
/** skip them undefined or function value */
wson_push_type_map(buffer, size - undefinedOrFunctionSize);
objectStack.append(object);
for(uint32_t i=0; i<size; i++){
Identifier& propertyName = objectPropertyNames[i];
if(methodTable->getOwnPropertySlot(object, exec, propertyName, slot)){
JSValue propertyValue = slot.getValue(exec, propertyName);
if(propertyValue.isUndefined() || propertyValue.isFunction()){
continue;
}
if(propertyValue.isObject()){
propertyValue = call_object_js_value_to_json(exec, propertyValue, vm, &propertyName);
}
wson_push_js_identifier(propertyName , buffer);
wson_push_js_value(exec, propertyValue, buffer,objectStack);
}
}
objectStack.removeLast();
return;
}
if(val.isBoolean()){
if(val.isTrue()){
wson_push_type_boolean(buffer, 1);
}else{
wson_push_type_boolean(buffer, 0);
}
return;
}
if(val.isFunction()){
wson_push_type_null(buffer);
return;
}
#ifdef __ANDROID__
LOGE("weex wson err value type is not handled, treat as null, json value %s %d %d ", JSONStringify(exec, val, 0).utf8().data(), val.isFunction(), val.tag());
for(size_t i=0; i<objectStack.size(); i++){
LOGE("weex wson err value type is not handled, treat as null, root json value %s", JSONStringify(exec, objectStack[i], 0).utf8().data());
}
#endif
wson_push_type_null(buffer);
}
inline void wson_push_js_string(ExecState* exec, JSValue val, wson_buffer* buffer){
String s = val.toWTFString(exec);
size_t length = s.length();
if (s.is8Bit()) {
// Convert latin1 chars to unicode.
wson_push_type(buffer, WSON_STRING_TYPE);
wson_push_uint(buffer, length*sizeof(UChar));
wson_buffer_require(buffer, length*sizeof(UChar));
UChar* jchars = (UChar*)((uint8_t*)buffer->data + buffer->position);
for (unsigned i = 0; i < length; i++) {
#ifdef __ANDROID__
jchars[i] = s.at(i);
#else
jchars[i] = s.characterAt(i);
#endif
}
buffer->position +=length*sizeof(UChar);
} else {
wson_push_type(buffer, WSON_STRING_TYPE);
wson_push_uint(buffer, length*sizeof(UChar));
wson_push_bytes(buffer, s.characters16(), s.length()*sizeof(UChar));
}
}
inline void wson_push_js_identifier(Identifier val, wson_buffer* buffer){
String s = val.string();
size_t length = s.length();
if (s.is8Bit()) {
// Convert latin1 chars to unicode.
wson_push_uint(buffer, length*sizeof(UChar));
wson_buffer_require(buffer, length*sizeof(UChar));
UChar* jchars = (UChar*)((uint8_t*)buffer->data + buffer->position);
for (unsigned i = 0; i < length; i++) {
#ifdef __ANDROID__
jchars[i] = s.at(i);
#else
jchars[i] = s.characterAt(i);
#endif
}
buffer->position +=length*sizeof(UChar);
} else {
wson_push_uint(buffer, length*sizeof(UChar));
wson_push_bytes(buffer, s.characters16(), s.length()*sizeof(UChar));
}
}
}