From 315477b0450575f5e6cba1a1c7f209c9249844a0 Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Thu, 29 Jun 2017 15:41:06 -0700 Subject: [PATCH 1/3] Update files from node * Added `node_internals.cc/h` to shim missing internal functions --- src/node_api.cc | 208 ++++++++++++++++++++++++++++++++++++------ src/node_api.gyp | 1 + src/node_api.h | 18 +++- src/node_api_types.h | 2 +- src/node_internals.cc | 97 ++++++++++++++++++++ src/node_internals.h | 69 ++++++++++++++ 6 files changed, 366 insertions(+), 29 deletions(-) create mode 100644 src/node_internals.cc create mode 100644 src/node_internals.h diff --git a/src/node_api.cc b/src/node_api.cc index 6dcd0a7c7..becafc468 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -17,6 +17,9 @@ #include #include "uv.h" #include "node_api.h" +#include "node_internals.h" + +#define NAPI_VERSION 1 static napi_status napi_set_last_error(napi_env env, napi_status error_code, @@ -31,14 +34,32 @@ struct napi_env__ { ~napi_env__() { last_exception.Reset(); has_instance.Reset(); + wrap_template.Reset(); + function_data_template.Reset(); + accessor_data_template.Reset(); } v8::Isolate* isolate; v8::Persistent last_exception; v8::Persistent has_instance; + v8::Persistent wrap_template; + v8::Persistent function_data_template; + v8::Persistent accessor_data_template; bool has_instance_available; napi_extended_error_info last_error; }; +#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \ + do { \ + if ((env)->prefix ## _template.IsEmpty()) { \ + (destination) = v8::ObjectTemplate::New(isolate); \ + (destination)->SetInternalFieldCount((field_count)); \ + (env)->prefix ## _template.Reset(isolate, (destination)); \ + } else { \ + (destination) = env->prefix ## _template.Get(isolate); \ + } \ + } while (0) + + #define RETURN_STATUS_IF_FALSE(env, condition, status) \ do { \ if (!(condition)) { \ @@ -154,14 +175,20 @@ class HandleScopeWrapper { // across different versions. class EscapableHandleScopeWrapper { public: - explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} + explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) + : scope(isolate), escape_called_(false) {} + bool escape_called() const { + return escape_called_; + } template v8::Local Escape(v8::Local handle) { + escape_called_ = true; return scope.Escape(handle); } private: v8::EscapableHandleScope scope; + bool escape_called_; }; napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { @@ -594,8 +621,8 @@ v8::Local CreateFunctionCallbackData(napi_env env, v8::Isolate* isolate = env->isolate; v8::Local context = isolate->GetCurrentContext(); - v8::Local otpl = v8::ObjectTemplate::New(isolate); - otpl->SetInternalFieldCount(v8impl::kFunctionFieldCount); + v8::Local otpl; + ENV_OBJECT_TEMPLATE(env, function_data, otpl, v8impl::kFunctionFieldCount); v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); cbdata->SetInternalField( @@ -620,8 +647,8 @@ v8::Local CreateAccessorCallbackData(napi_env env, v8::Isolate* isolate = env->isolate; v8::Local context = isolate->GetCurrentContext(); - v8::Local otpl = v8::ObjectTemplate::New(isolate); - otpl->SetInternalFieldCount(v8impl::kAccessorFieldCount); + v8::Local otpl; + ENV_OBJECT_TEMPLATE(env, accessor_data, otpl, v8impl::kAccessorFieldCount); v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); cbdata->SetInternalField( @@ -646,6 +673,38 @@ v8::Local CreateAccessorCallbackData(napi_env env, return cbdata; } +// Pointer used to identify items wrapped by N-API. Used by FindWrapper and +// napi_wrap(). +const char napi_wrap_name[] = "N-API Wrapper"; + +// Search the object's prototype chain for the wrapper object. Usually the +// wrapper would be the first in the chain, but it is OK for other objects to +// be inserted in the prototype chain. +bool FindWrapper(v8::Local obj, + v8::Local* result = nullptr) { + v8::Local wrapper = obj; + + do { + v8::Local proto = wrapper->GetPrototype(); + if (proto.IsEmpty() || !proto->IsObject()) { + return false; + } + wrapper = proto.As(); + if (wrapper->InternalFieldCount() == 2) { + v8::Local external = wrapper->GetInternalField(1); + if (external->IsExternal() && + external.As()->Value() == v8impl::napi_wrap_name) { + break; + } + } + } while (true); + + if (result != nullptr) { + *result = wrapper; + } + return true; +} + } // end of namespace v8impl // Intercepts the Node-V8 module registration callback. Converts parameters @@ -716,7 +775,8 @@ const char* error_messages[] = {nullptr, "An array was expected", "Unknown failure", "An exception is pending", - "The async work item was cancelled"}; + "The async work item was cancelled", + "napi_escape_handle already called on scope"}; static napi_status napi_clear_last_error(napi_env env) { CHECK_ENV(env); @@ -744,10 +804,14 @@ napi_status napi_get_last_error_info(napi_env env, CHECK_ENV(env); CHECK_ARG(env, result); + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. static_assert( - (sizeof (error_messages) / sizeof (*error_messages)) == napi_status_last, + node::arraysize(error_messages) == napi_escape_called_twice + 1, "Count of error messages must match count of error values"); - assert(env->last_error.error_code < napi_status_last); + assert(env->last_error.error_code <= napi_escape_called_twice); // Wait until someone requests the last error information to fetch the error // message string @@ -817,9 +881,6 @@ napi_status napi_define_class(napi_env env, v8::Local tpl = v8::FunctionTemplate::New( isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); - // we need an internal field to stash the wrapped object - tpl->InstanceTemplate()->SetInternalFieldCount(1); - v8::Local name_string; CHECK_NEW_FROM_UTF8(env, name_string, utf8name); tpl->SetClassName(name_string); @@ -991,6 +1052,49 @@ napi_status napi_get_property(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, k); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != NULL) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + +NAPI_EXTERN napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + RETURN_STATUS_IF_FALSE(env, k->IsName(), napi_name_expected); + v8::Maybe has_maybe = obj->HasOwnProperty(context, k.As()); + CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); + *result = has_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, @@ -1128,6 +1232,26 @@ napi_status napi_get_element(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, index); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != NULL) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, @@ -1948,14 +2072,34 @@ napi_status napi_wrap(napi_env env, CHECK_ARG(env, js_object); v8::Isolate* isolate = env->isolate; - v8::Local obj = - v8impl::V8LocalValueFromJsValue(js_object).As(); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); + RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); + v8::Local obj = value.As(); - // Only objects that were created from a NAPI constructor's prototype - // via napi_define_class() can be (un)wrapped. - RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg); + // If we've already wrapped this object, we error out. + RETURN_STATUS_IF_FALSE(env, !v8impl::FindWrapper(obj), napi_invalid_arg); - obj->SetInternalField(0, v8::External::New(isolate, native_object)); + // Create a wrapper object with an internal field to hold the wrapped pointer + // and a second internal field to identify the owner as N-API. + v8::Local wrapper_template; + ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, 2); + + auto maybe_object = wrapper_template->NewInstance(context); + CHECK_MAYBE_EMPTY(env, maybe_object, napi_generic_failure); + + v8::Local wrapper = maybe_object.ToLocalChecked(); + wrapper->SetInternalField(1, v8::External::New(isolate, + reinterpret_cast(const_cast(v8impl::napi_wrap_name)))); + + // Store the pointer as an external in the wrapper. + wrapper->SetInternalField(0, v8::External::New(isolate, native_object)); + + // Insert the wrapper into the object's prototype chain. + v8::Local proto = obj->GetPrototype(); + CHECK(wrapper->SetPrototype(context, proto).FromJust()); + CHECK(obj->SetPrototype(context, wrapper).FromJust()); if (result != nullptr) { // The returned reference should be deleted via napi_delete_reference() @@ -1986,11 +2130,11 @@ napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); v8::Local obj = value.As(); - // Only objects that were created from a NAPI constructor's prototype - // via napi_define_class() can be (un)wrapped. - RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg); + v8::Local wrapper; + RETURN_STATUS_IF_FALSE( + env, v8impl::FindWrapper(obj, &wrapper), napi_invalid_arg); - v8::Local unwrappedValue = obj->GetInternalField(0); + v8::Local unwrappedValue = wrapper->GetInternalField(0); RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg); *result = unwrappedValue.As()->Value(); @@ -2195,9 +2339,12 @@ napi_status napi_escape_handle(napi_env env, v8impl::EscapableHandleScopeWrapper* s = v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); - *result = v8impl::JsValueFromV8LocalValue( - s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); - return napi_clear_last_error(env); + if (!s->escape_called()) { + *result = v8impl::JsValueFromV8LocalValue( + s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); + return napi_clear_last_error(env); + } + return napi_set_last_error(env, napi_escape_called_twice); } napi_status napi_new_instance(napi_env env, @@ -2250,7 +2397,7 @@ napi_status napi_instanceof(napi_env env, } if (env->has_instance_available) { - napi_value value, js_result, has_instance = nullptr; + napi_value value, js_result = nullptr, has_instance = nullptr; napi_status status = napi_generic_failure; napi_valuetype value_type; @@ -2530,7 +2677,7 @@ napi_status napi_create_arraybuffer(napi_env env, v8::ArrayBuffer::New(isolate, byte_length); // Optionally return a pointer to the buffer's data, to avoid another call to - // retreive it. + // retrieve it. if (data != nullptr) { *data = buffer->GetContents().Data(); } @@ -2713,6 +2860,13 @@ napi_status napi_get_typedarray_info(napi_env env, return napi_clear_last_error(env); } +napi_status napi_get_version(napi_env env, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = NAPI_VERSION; + return napi_clear_last_error(env); +} + namespace uvimpl { static napi_status ConvertUVErrorCode(int code) { @@ -2781,7 +2935,7 @@ class Work { // report it as a fatal exception. (There is no JavaScript on the // callstack that can possibly handle it.) if (!env->last_exception.IsEmpty()) { - v8::TryCatch try_catch; + v8::TryCatch try_catch(env->isolate); env->isolate->ThrowException( v8::Local::New(env->isolate, env->last_exception)); node::FatalException(env->isolate, try_catch); diff --git a/src/node_api.gyp b/src/node_api.gyp index a23d9f581..07921db26 100644 --- a/src/node_api.gyp +++ b/src/node_api.gyp @@ -8,6 +8,7 @@ 'type': 'static_library', 'sources': [ 'node_api.cc', + 'node_internals.cc', ], 'defines': [ 'EXTERNAL_NAPI', diff --git a/src/node_api.h b/src/node_api.h index 29c5c513f..e346b762d 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -226,6 +226,14 @@ NAPI_EXTERN napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value* result); +NAPI_EXTERN napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); NAPI_EXTERN napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, @@ -250,6 +258,10 @@ NAPI_EXTERN napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result); +NAPI_EXTERN napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); NAPI_EXTERN napi_status napi_define_properties(napi_env env, napi_value object, @@ -478,6 +490,10 @@ NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, napi_async_work work); + +// version management +NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result); + EXTERN_C_END -#endif // SRC_NODE_API_H__ +#endif // SRC_NODE_API_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h index 4bf1b8263..43102c519 100644 --- a/src/node_api_types.h +++ b/src/node_api_types.h @@ -67,7 +67,7 @@ typedef enum { napi_generic_failure, napi_pending_exception, napi_cancelled, - napi_status_last + napi_escape_called_twice } napi_status; typedef napi_value (*napi_callback)(napi_env env, diff --git a/src/node_internals.cc b/src/node_internals.cc new file mode 100644 index 000000000..19131c51f --- /dev/null +++ b/src/node_internals.cc @@ -0,0 +1,97 @@ +#include "node_internals.h" +#include +#include +#include +#include "uv.h" + +#if defined(_MSC_VER) +#define getpid GetCurrentProcessId +#else +#include // getpid +#endif + +namespace node { + +static void PrintErrorString(const char* format, ...) { + va_list ap; + va_start(ap, format); +#ifdef _WIN32 + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + + // Check if stderr is something other than a tty/console + if (stderr_handle == INVALID_HANDLE_VALUE || + stderr_handle == nullptr || + uv_guess_handle(_fileno(stderr)) != UV_TTY) { + vfprintf(stderr, format, ap); + va_end(ap); + return; + } + + // Fill in any placeholders + int n = _vscprintf(format, ap); + std::vector out(n + 1); + vsprintf(out.data(), format, ap); + + // Get required wide buffer size + n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); + + std::vector wbuf(n); + MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); + + // Don't include the null character in the output + CHECK_GT(n, 0); + WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); +#else + vfprintf(stderr, format, ap); +#endif + va_end(ap); +} + +void DumpBacktrace(FILE* fp) { +} + +NO_RETURN void Abort() { + DumpBacktrace(stderr); + fflush(stderr); + ABORT_NO_BACKTRACE(); +} + +NO_RETURN void Assert(const char* const (*args)[4]) { + auto filename = (*args)[0]; + auto linenum = (*args)[1]; + auto message = (*args)[2]; + auto function = (*args)[3]; + + char exepath[256]; + size_t exepath_size = sizeof(exepath); + if (uv_exepath(exepath, &exepath_size)) + snprintf(exepath, sizeof(exepath), "node"); + + char pid[12] = {0}; + snprintf(pid, sizeof(pid), "[%u]", getpid()); + + fprintf(stderr, "%s%s: %s:%s:%s%s Assertion `%s' failed.\n", + exepath, pid, filename, linenum, + function, *function ? ":" : "", message); + fflush(stderr); + + Abort(); +} + +static void OnFatalError(const char* location, const char* message) { + if (location) { + PrintErrorString("FATAL ERROR: %s %s\n", location, message); + } else { + PrintErrorString("FATAL ERROR: %s\n", message); + } + fflush(stderr); + ABORT(); +} + +NO_RETURN void FatalError(const char* location, const char* message) { + OnFatalError(location, message); + // to suppress compiler warning + ABORT(); +} + +} // namespace node diff --git a/src/node_internals.h b/src/node_internals.h new file mode 100644 index 000000000..785520f30 --- /dev/null +++ b/src/node_internals.h @@ -0,0 +1,69 @@ +#ifndef SRC_NODE_INTERNALS_H_ +#define SRC_NODE_INTERNALS_H_ + +// +// This is a stripped down shim to allow node_api.cc to build outside of the node source tree. +// + +#include + +// Windows 8+ does not like abort() in Release mode +#ifdef _WIN32 +#define ABORT_NO_BACKTRACE() raise(SIGABRT) +#else +#define ABORT_NO_BACKTRACE() abort() +#endif + +#define ABORT() node::Abort() + +#ifdef __GNUC__ +#define LIKELY(expr) __builtin_expect(!!(expr), 1) +#define UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ +#else +#define LIKELY(expr) expr +#define UNLIKELY(expr) expr +#define PRETTY_FUNCTION_NAME "" +#endif + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define CHECK(expr) \ + do { \ + if (UNLIKELY(!(expr))) { \ + static const char* const args[] = { __FILE__, STRINGIFY(__LINE__), \ + #expr, PRETTY_FUNCTION_NAME }; \ + node::Assert(&args); \ + } \ + } while (0) + +#define CHECK_EQ(a, b) CHECK((a) == (b)) +#define CHECK_GE(a, b) CHECK((a) >= (b)) +#define CHECK_GT(a, b) CHECK((a) > (b)) +#define CHECK_LE(a, b) CHECK((a) <= (b)) +#define CHECK_LT(a, b) CHECK((a) < (b)) +#define CHECK_NE(a, b) CHECK((a) != (b)) + +#ifdef __GNUC__ +#define NO_RETURN __attribute__((noreturn)) +#else +#define NO_RETURN +#endif + +namespace node { + +// The slightly odd function signature for Assert() is to ease +// instruction cache pressure in calls from ASSERT and CHECK. +NO_RETURN void Abort(); +NO_RETURN void Assert(const char* const (*args)[4]); +void DumpBacktrace(FILE* fp); + +template +constexpr size_t arraysize(const T(&)[N]) { return N; } + +NO_RETURN void FatalError(const char* location, const char* message); + +} // namespace node + +#endif // SRC_NODE_INTERNALS_H_ From 4686a4a79f490a3ed1109eaf1b0c3e1adf758dea Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Wed, 12 Jul 2017 17:42:53 -0700 Subject: [PATCH 2/3] Fixing build issue for node 4 --- src/node_api.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node_api.cc b/src/node_api.cc index becafc468..81f0e3e54 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -55,7 +55,8 @@ struct napi_env__ { (destination)->SetInternalFieldCount((field_count)); \ (env)->prefix ## _template.Reset(isolate, (destination)); \ } else { \ - (destination) = env->prefix ## _template.Get(isolate); \ + (destination) = v8::Local::New( \ + isolate, env->prefix ## _template); \ } \ } while (0) From 4011c960bcf30904f7bbe5be5ba537f55e0bee97 Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Thu, 29 Jun 2017 15:41:06 -0700 Subject: [PATCH 3/3] Add new node shims * Added `util.h` and `util-inl.h` for incoming changes to error helpers --- src/node_internals.h | 1 + src/util-inl.h | 38 ++++++++++++++++++++++++++++++++++++++ src/util.h | 7 +++++++ 3 files changed, 46 insertions(+) create mode 100644 src/util-inl.h create mode 100644 src/util.h diff --git a/src/node_internals.h b/src/node_internals.h index 785520f30..11492c0b4 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -5,6 +5,7 @@ // This is a stripped down shim to allow node_api.cc to build outside of the node source tree. // +#include "util-inl.h" #include // Windows 8+ does not like abort() in Release mode diff --git a/src/util-inl.h b/src/util-inl.h new file mode 100644 index 000000000..30aad168f --- /dev/null +++ b/src/util-inl.h @@ -0,0 +1,38 @@ +#ifndef SRC_UTIL_INL_H_ +#define SRC_UTIL_INL_H_ + +#include "util.h" +#include "v8.h" + +namespace node { + +inline v8::Local OneByteString(v8::Isolate* isolate, + const char* data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length).ToLocalChecked(); +} + +inline v8::Local OneByteString(v8::Isolate* isolate, + const signed char* data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length).ToLocalChecked(); +} + +inline v8::Local OneByteString(v8::Isolate* isolate, + const unsigned char* data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length).ToLocalChecked(); +} + +} // namespace node + +#endif // SRC_UTIL_INL_H_ diff --git a/src/util.h b/src/util.h new file mode 100644 index 000000000..6765bc1ba --- /dev/null +++ b/src/util.h @@ -0,0 +1,7 @@ +#ifndef SRC_UTIL_H_ +#define SRC_UTIL_H_ + +#define FIXED_ONE_BYTE_STRING(isolate, string) \ + (node::OneByteString((isolate), (string), sizeof(string) - 1)) + +#endif // SRC_UTIL_H_