1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/builtins/builtins_json.h"
17
18#include "ecmascript/base/json_parser.h"
19#include "ecmascript/base/json_stringifier.h"
20
21namespace panda::ecmascript::builtins {
22namespace {
23using TransformType = base::JsonHelper::TransformType;
24using ParseOptions =  base::JsonHelper::ParseOptions;
25using ParseReturnType = base::JsonHelper::ParseReturnType;
26using BigIntMode = base::JsonHelper::BigIntMode;
27
28void InitWithTransformType(JSHandle<GlobalEnv> &env, TransformType transformType,
29                           JSMutableHandle<JSFunction> &constructor, SCheckMode &sCheckMode)
30{
31    if (transformType == TransformType::NORMAL || transformType == TransformType::BIGINT) {
32        sCheckMode = SCheckMode::CHECK;
33        constructor.Update(env->GetObjectFunction());
34    } else {
35        sCheckMode = SCheckMode::SKIP;
36        constructor.Update(env->GetSObjectFunction());
37    }
38}
39}  // namespace
40
41using Internalize = base::Internalize;
42
43JSTaggedValue BuiltinsJson::Parse(EcmaRuntimeCallInfo *argv)
44{
45    return ParseWithTransformType(argv, TransformType::NORMAL);
46}
47
48JSTaggedValue BuiltinsSendableJson::Parse(EcmaRuntimeCallInfo *argv)
49{
50    uint32_t argc = argv->GetArgsNumber();
51    if (argc >= 2) { // 2: two args
52        JSHandle<JSTaggedValue> reviverVal = GetCallArg(argv, 1);
53        if (!reviverVal->IsUndefined()) {
54            THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), GET_MESSAGE_STRING(ReviverOnlySupportUndefined),
55                                        JSTaggedValue::Exception());
56        }
57    }
58    return BuiltinsJson::ParseWithTransformType(argv, TransformType::SENDABLE);
59}
60
61JSTaggedValue BuiltinsBigIntJson::Parse(EcmaRuntimeCallInfo *argv)
62{
63    return BuiltinsJson::ParseWithTransformType(argv, TransformType::BIGINT);
64}
65
66// 24.5.1
67JSTaggedValue BuiltinsJson::ParseWithTransformType(EcmaRuntimeCallInfo *argv, TransformType transformType)
68{
69    BUILTINS_API_TRACE(argv->GetThread(), Json, Parse);
70    ASSERT(argv);
71    JSThread *thread = argv->GetThread();
72    [[maybe_unused]] EcmaHandleScope handleScope(thread);
73
74    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
75
76    uint32_t argc = argv->GetArgsNumber();
77    if (argc == 0) {
78        JSHandle<JSObject> syntaxError = factory->GetJSError(base::ErrorType::SYNTAX_ERROR,
79                                                             "arg is empty", StackCheck::NO);
80        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, syntaxError.GetTaggedValue(), JSTaggedValue::Exception());
81    }
82
83    JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
84    JSMutableHandle<JSTaggedValue> reviverVal(thread, JSTaggedValue::Undefined());
85    ParseOptions parseOptions;
86    if (argc == 2) {  // 2: two args
87        reviverVal.Update(GetCallArg(argv, 1));
88    } else if (argc == 3 && base::JsonHelper::IsTypeSupportBigInt(transformType)) { // 3: three args
89        reviverVal.Update(GetCallArg(argv, 1));
90        JSHandle<JSTaggedValue> options = GetCallArg(argv, 2); // 2: two args
91        JSHandle<JSTaggedValue> modeKey(factory->NewFromStdString("bigIntMode"));
92        JSHandle<JSTaggedValue> typeKey(factory->NewFromStdString("parseReturnType"));
93        if (options->IsECMAObject()) {
94            JSHandle<JSTaggedValue> type = JSTaggedValue::GetProperty(thread, options, typeKey).GetValue();
95            if (transformType == TransformType::SENDABLE && type->IsInt() && type->GetInt() == 1) { // 1: map
96                parseOptions.returnType = ParseReturnType::MAP;
97            }
98            JSHandle<JSTaggedValue> modeValue = JSTaggedValue::GetProperty(thread, options, modeKey).GetValue();
99            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
100            if (modeValue->IsInt()) {
101                int val = modeValue->GetInt();
102                if (val == 2) { // 2: bigIntMode
103                    parseOptions.bigIntMode = BigIntMode::ALWAYS_PARSE_AS_BIGINT;
104                } else if (val == 1) {
105                    parseOptions.bigIntMode = BigIntMode::PARSE_AS_BIGINT;
106                }
107            }
108        }
109    }
110    return ParseWithTransformType(thread->GetEcmaVM(), msg, reviverVal, transformType, parseOptions);
111}
112
113JSTaggedValue BuiltinsJson::ParseWithTransformType(const EcmaVM *vm, JSHandle<JSTaggedValue> &msg,
114                                                   JSHandle<JSTaggedValue> &reviverVal, TransformType transformType,
115                                                   ParseOptions options)
116{
117    JSThread *thread = vm->GetJSThread();
118    [[maybe_unused]] EcmaHandleScope handleScope(thread);
119
120    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
121    JSHandle<EcmaString> parseString = JSTaggedValue::ToString(thread, msg);
122    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
123    JSHandle<JSTaggedValue> result;
124    if (EcmaStringAccessor(parseString).IsUtf8()) {
125        panda::ecmascript::base::Utf8JsonParser parser(thread, transformType, options);
126        result = parser.Parse(parseString);
127    } else {
128        panda::ecmascript::base::Utf16JsonParser parser(thread, transformType, options);
129        result = parser.Parse(*parseString);
130    }
131    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
132    JSTaggedValue reviver = JSTaggedValue::Undefined();
133    if (reviverVal->IsJSFunction()) {
134        reviver = reviverVal.GetTaggedValue();
135        if (reviver.IsCallable()) {
136            JSHandle<JSTaggedValue> callbackfnHandle(thread, reviver);
137            // Let root be ! OrdinaryObjectCreate(%Object.prototype%).
138            JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
139            JSMutableHandle<JSFunction> constructor(thread, JSTaggedValue::Undefined());
140            SCheckMode sCheckMode = SCheckMode::CHECK;
141            InitWithTransformType(env, transformType, constructor, sCheckMode);
142            JSHandle<JSObject> root = factory->NewJSObjectByConstructor(constructor);
143            // Let rootName be the empty String.
144            JSHandle<JSTaggedValue> rootName(factory->GetEmptyString());
145            // Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
146            bool success = JSObject::CreateDataProperty(thread, root, rootName, result, sCheckMode);
147            if (success) {
148                result = Internalize::InternalizeJsonProperty(thread, root, rootName, callbackfnHandle, transformType);
149                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
150            }
151        }
152    }
153    if (transformType == TransformType::SENDABLE) {
154        if (result->IsHeapObject() && !result->IsJSShared() && !result->IsString()) {
155            THROW_TYPE_ERROR_AND_RETURN(thread, GET_MESSAGE_STRING(ClassNotDerivedFromShared),
156                                        JSTaggedValue::Exception());
157        }
158    }
159    return result.GetTaggedValue();
160}
161
162// 24.5.2
163JSTaggedValue BuiltinsJson::Stringify(EcmaRuntimeCallInfo *argv)
164{
165    return BuiltinsJson::StringifyWithTransformType(argv, TransformType::NORMAL);
166}
167
168JSTaggedValue BuiltinsSendableJson::Stringify(EcmaRuntimeCallInfo *argv)
169{
170    return BuiltinsJson::StringifyWithTransformType(argv, TransformType::SENDABLE);
171}
172
173JSTaggedValue BuiltinsBigIntJson::Stringify(EcmaRuntimeCallInfo *argv)
174{
175    return BuiltinsJson::StringifyWithTransformType(argv, TransformType::BIGINT);
176}
177
178JSTaggedValue BuiltinsJson::StringifyWithTransformType(EcmaRuntimeCallInfo *argv, TransformType transformType)
179{
180    BUILTINS_API_TRACE(argv->GetThread(), Json, Stringify);
181    ASSERT(argv);
182    JSThread *thread = argv->GetThread();
183    [[maybe_unused]] EcmaHandleScope handleScope(thread);
184
185    uint32_t argc = argv->GetArgsNumber();
186    JSTaggedValue value = GetCallArg(argv, 0).GetTaggedValue();
187    JSTaggedValue replacer = JSTaggedValue::Undefined();
188    JSTaggedValue gap = JSTaggedValue::Undefined();
189
190    if (argc == 2) {  // 2: 2 args
191        replacer = GetCallArg(argv, 1).GetTaggedValue();
192    } else if (argc == 3) {  // 3: 3 args
193        replacer = GetCallArg(argv, 1).GetTaggedValue();
194        gap = GetCallArg(argv, BuiltinsBase::ArgsPosition::THIRD).GetTaggedValue();
195    }
196
197    JSHandle<JSTaggedValue> handleValue(thread, value);
198    JSHandle<JSTaggedValue> handleReplacer(thread, replacer);
199    JSHandle<JSTaggedValue> handleGap(thread, gap);
200    panda::ecmascript::base::JsonStringifier stringifier(thread, transformType);
201    JSHandle<JSTaggedValue> result = stringifier.Stringify(handleValue, handleReplacer, handleGap);
202
203    return result.GetTaggedValue();
204}
205}  // namespace panda::ecmascript::builtins
206