1 /*
2  * Copyright (c) 2022 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 "timer.h"
17 
18 #include "native_engine/native_engine.h"
19 #include "tools/log.h"
20 
21 #ifdef ENABLE_CONTAINER_SCOPE
22 using OHOS::Ace::ContainerScope;
23 #endif
24 
25 namespace OHOS::JsSysModule {
26 using namespace Commonlibrary::Concurrent::Common;
27 
28 uint32_t Timer::timeCallbackId = 0;
29 uint32_t deleteTimerCount = 0;
30 std::map<uint32_t, TimerCallbackInfo*> Timer::timerTable;
31 std::mutex Timer::timeLock;
32 
~TimerCallbackInfo()33 TimerCallbackInfo::~TimerCallbackInfo()
34 {
35     Helper::NapiHelper::DeleteReference(env_, callback_);
36     for (size_t idx = 0; idx < argc_; idx++) {
37         Helper::NapiHelper::DeleteReference(env_, argv_[idx]);
38     }
39     Helper::CloseHelp::DeletePointer(argv_, true);
40 
41     uv_timer_stop(timeReq_);
42     uv_close(reinterpret_cast<uv_handle_t*>(timeReq_), [](uv_handle_t* handle) {
43         if (handle != nullptr) {
44             delete (uv_timer_t*)handle;
45             handle = nullptr;
46         }
47     });
48 }
49 
RegisterTime(napi_env env)50 bool Timer::RegisterTime(napi_env env)
51 {
52     if (env == nullptr) {
53         return false;
54     }
55     napi_property_descriptor properties[] = {
56         DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("setTimeout", SetTimeout),
57         DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("setInterval", SetInterval),
58         DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearTimeout", ClearTimer),
59         DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearInterval", ClearTimer)
60     };
61     napi_value globalObj = Helper::NapiHelper::GetGlobalObject(env);
62     napi_status status = napi_define_properties(env, globalObj, sizeof(properties) / sizeof(properties[0]), properties);
63     return status == napi_ok;
64 }
65 
SetTimeout(napi_env env, napi_callback_info cbinfo)66 napi_value Timer::SetTimeout(napi_env env, napi_callback_info cbinfo)
67 {
68     return Timer::SetTimeoutInner(env, cbinfo, false);
69 }
70 
SetInterval(napi_env env, napi_callback_info cbinfo)71 napi_value Timer::SetInterval(napi_env env, napi_callback_info cbinfo)
72 {
73     return Timer::SetTimeoutInner(env, cbinfo, true);
74 }
75 
ClearTimer(napi_env env, napi_callback_info cbinfo)76 napi_value Timer::ClearTimer(napi_env env, napi_callback_info cbinfo)
77 {
78     // 1. check args
79     size_t argc = 1;
80     napi_value argv[1];
81     napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
82     if (argc < 1) {
83         HILOG_ERROR("the number of params must be one");
84         return nullptr;
85     }
86 
87     uint32_t tId;
88     napi_status status = napi_get_value_uint32(env, argv[0], &tId);
89     if (status != napi_ok) {
90         HILOG_DEBUG("first param should be number");
91         return nullptr;
92     }
93 
94     {
95         std::lock_guard<std::mutex> lock(timeLock);
96         auto iter = timerTable.find(tId);
97         if (iter == timerTable.end()) {
98             // timer already cleared
99             return nullptr;
100         }
101         TimerCallbackInfo* callbackInfo = iter->second;
102         if (callbackInfo->env_ != env) {
103             HILOG_ERROR("Timer is deleting by another thread, please check js code. TimerID:%{public}u", tId);
104         } else {
105             timerTable.erase(tId);
106             Helper::CloseHelp::DeletePointer(callbackInfo, false);
107             HILOG_INFO("DeleteTimer ID: %{public}u, count: %{public}u", tId, ++deleteTimerCount);
108         }
109     }
110     return Helper::NapiHelper::GetUndefinedValue(env);
111 }
112 
TimerCallback(uv_timer_t* handle)113 void Timer::TimerCallback(uv_timer_t* handle)
114 {
115     TimerCallbackInfo* callbackInfo = static_cast<TimerCallbackInfo*>(handle->data);
116     if (callbackInfo == nullptr) {
117         return;
118     }
119     // Save the following parameters to ensure that they can still obtained if callback clears the callbackInfo.
120     bool repeat = callbackInfo->repeat_;
121     uint32_t tId = callbackInfo->tId_;
122     napi_env env = callbackInfo->env_;
123 #ifdef ENABLE_CONTAINER_SCOPE
124     ContainerScope containerScope(callbackInfo->containerScopeId_);
125 #endif
126 
127     napi_handle_scope scope = nullptr;
128     napi_open_handle_scope(env, &scope);
129     if (scope == nullptr) {
130         return;
131     }
132     napi_value callback = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->callback_);
133     napi_value undefinedValue = Helper::NapiHelper::GetUndefinedValue(env);
134     napi_value callbackResult = nullptr;
135     napi_value* callbackArgv = new napi_value[callbackInfo->argc_];
136     for (size_t idx = 0; idx < callbackInfo->argc_; idx++) {
137         callbackArgv[idx] = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->argv_[idx]);
138     }
139 
140     napi_call_function(env, undefinedValue, callback,
141                        callbackInfo->argc_, callbackArgv, &callbackResult);
142     Helper::CloseHelp::DeletePointer(callbackArgv, true);
143     bool isExceptionPending = false;
144     napi_is_exception_pending(env, &isExceptionPending);
145     NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
146     if (isExceptionPending) {
147         HILOG_ERROR("Pending exception in TimerCallback. Triggering HandleUncaughtException");
148         // worker will handle exception itself
149         if (engine->IsMainThread()) {
150             engine->HandleUncaughtException();
151         }
152         napi_close_handle_scope(env, scope);
153         DeleteTimer(tId, callbackInfo);
154         return;
155     }
156 
157     // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
158     std::lock_guard<std::mutex> lock(timeLock);
159     if (timerTable.find(tId) == timerTable.end()) {
160         napi_close_handle_scope(env, scope);
161         return;
162     }
163     if (!repeat) {
164         timerTable.erase(tId);
165         napi_close_handle_scope(env, scope);
166         Helper::CloseHelp::DeletePointer(callbackInfo, false);
167     } else {
168         napi_close_handle_scope(env, scope);
169         uv_timer_again(handle);
170     }
171 }
172 
SetTimeoutInnerCore(napi_env env, napi_value* argv, size_t argc, bool repeat)173 napi_value Timer::SetTimeoutInnerCore(napi_env env, napi_value* argv, size_t argc, bool repeat)
174 {
175     int32_t timeout = 0;
176     if (argc > 1) {
177         napi_status status = napi_get_value_int32(env, argv[1], &timeout);
178         if (status != napi_ok) {
179             HILOG_WARN("timeout should be number");
180             timeout = 0;
181         }
182     }
183     if (timeout < 0) {
184         HILOG_DEBUG("timeout < 0 is unreasonable");
185     }
186     // 2. get callback args
187     size_t callbackArgc = argc >= 2 ? argc - 2 : 0; // 2 include callback and timeout
188     napi_ref* callbackArgv = nullptr;
189     if (callbackArgc > 0) {
190         callbackArgv = new napi_ref[callbackArgc];
191         for (size_t idx = 0; idx < callbackArgc; idx++) {
192             callbackArgv[idx] =
193                 Helper::NapiHelper::CreateReference(env, argv[idx + 2], 1); // 2 include callback and timeout
194         }
195     }
196     // 3. generate time callback id
197     // 4. generate time callback info
198     // 5. push callback info into timerTable
199     uint32_t tId = 0;
200     TimerCallbackInfo* callbackInfo = nullptr;
201     {
202         std::lock_guard<std::mutex> lock(timeLock);
203         tId = timeCallbackId++;
204         napi_ref callbackRef = Helper::NapiHelper::CreateReference(env, argv[0], 1);
205         callbackInfo = new TimerCallbackInfo(env, tId, timeout, callbackRef, repeat, callbackArgc, callbackArgv);
206 #ifdef ENABLE_CONTAINER_SCOPE
207         callbackInfo->containerScopeId_ = ContainerScope::CurrentId();
208 #endif
209         if (timerTable.find(tId) != timerTable.end()) {
210             HILOG_ERROR("timerTable occurs error");
211         } else {
212             timerTable[tId] = callbackInfo;
213         }
214     }
215 
216     HILOG_DEBUG("SetTimeoutInnerCore function call before libuv! tId = %{public}u,timeout = %{public}u", tId, timeout);
217     // 6. start timer
218     uv_loop_t* loop = Helper::NapiHelper::GetLibUV(env);
219     NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
220     uv_update_time(loop);
221     uv_timer_start(callbackInfo->timeReq_, TimerCallback, timeout >= 0 ? timeout : 1, timeout > 0 ? timeout : 1);
222     if (engine->IsMainThread()) {
223         uv_async_send(&loop->wq_async);
224     } else {
225         uv_work_t *work = new uv_work_t;
226         uv_queue_work_with_qos(loop, work, [](uv_work_t *) {},
227                                [](uv_work_t *work, int32_t) {delete work; }, uv_qos_user_initiated);
228     }
229     return Helper::NapiHelper::CreateUint32(env, tId);
230 }
231 
SetTimeoutInner(napi_env env, napi_callback_info cbinfo, bool repeat)232 napi_value Timer::SetTimeoutInner(napi_env env, napi_callback_info cbinfo, bool repeat)
233 {
234     size_t argc = Helper::NapiHelper::GetCallbackInfoArgc(env, cbinfo);
235     if (argc < 1) {
236         napi_throw_error(env, nullptr, "StartTimeoutOrInterval, callback info is nullptr.");
237         return nullptr;
238     }
239     napi_value* argv = new napi_value[argc];
240     Helper::ObjectScope<napi_value> scope(argv, true);
241     napi_value thisVar = nullptr;
242     napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, nullptr);
243     if (!Helper::NapiHelper::IsCallable(env, argv[0])) {
244         HILOG_ERROR("Set callback timer failed with invalid parameter.");
245         return Helper::NapiHelper::GetUndefinedValue(env);
246     }
247 
248     return SetTimeoutInnerCore(env, argv, argc, repeat);
249 }
250 
ClearEnvironmentTimer(napi_env env)251 void Timer::ClearEnvironmentTimer(napi_env env)
252 {
253     std::lock_guard<std::mutex> lock(timeLock);
254     auto iter = timerTable.begin();
255     while (iter != timerTable.end()) {
256         TimerCallbackInfo* callbackInfo = iter->second;
257         if (callbackInfo->env_ == env) {
258             iter = timerTable.erase(iter);
259             Helper::CloseHelp::DeletePointer(callbackInfo, false);
260         } else {
261             iter++;
262         }
263     }
264 }
265 
HasTimer(napi_env env)266 bool Timer::HasTimer(napi_env env)
267 {
268     std::lock_guard<std::mutex> lock(timeLock);
269     auto iter = std::find_if(timerTable.begin(), timerTable.end(), [env](const auto &item) {
270         return item.second->env_ == env;
271     });
272     return iter != timerTable.end();
273 }
274 
DeleteTimer(uint32_t tId, TimerCallbackInfo* callbackInfo)275 void Timer::DeleteTimer(uint32_t tId, TimerCallbackInfo* callbackInfo)
276 {
277     std::lock_guard<std::mutex> lock(timeLock);
278     // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
279     if (timerTable.find(tId) != timerTable.end()) {
280         timerTable.erase(tId);
281         Helper::CloseHelp::DeletePointer(callbackInfo, false);
282     }
283 }
284 } // namespace Commonlibrary::JsSysModule
285