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