From 22b9a87845a17492a955ea2d3ab1a0558429b509 Mon Sep 17 00:00:00 2001
From: cjihrig <cjihrig@gmail.com>
Date: Mon, 26 Jun 2017 14:31:55 -0400
Subject: [PATCH] n-api: add napi_delete_property()

Fixes: https://github.com/nodejs/node/issues/13924
PR-URL: https://github.com/nodejs/node/pull/13934
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jason Ginchereau <jasongin@microsoft.com>
---
 doc/api/n-api.md                           | 23 +++++++++++
 src/node_api.cc                            | 22 +++++++++++
 src/node_api.h                             |  4 ++
 test/addons-napi/test_object/test.js       | 44 ++++++++++++++++++++++
 test/addons-napi/test_object/test_object.c | 26 +++++++++++++
 5 files changed, 119 insertions(+)

diff --git a/doc/api/n-api.md b/doc/api/n-api.md
index 5c6654f97cfdd0..831a02cf3dfa82 100644
--- a/doc/api/n-api.md
+++ b/doc/api/n-api.md
@@ -2276,6 +2276,28 @@ Returns `napi_ok` if the API succeeded.
 This API checks if the Object passed in has the named property.
 
 
+#### *napi_delete_property*
+<!-- YAML
+added: REPLACEME
+-->
+```C
+napi_status napi_delete_property(napi_env env,
+                                 napi_value object,
+                                 napi_value key,
+                                 bool* result);
+```
+
+- `[in] env`: The environment that the N-API call is invoked under.
+- `[in] object`: The object to query.
+- `[in] key`: The name of the property to delete.
+- `[out] result`: Whether the property deletion succeeded or not. `result` can
+optionally be ignored by passing `NULL`.
+
+Returns `napi_ok` if the API succeeded.
+
+This API attempts to delete the `key` own property from `object`.
+
+
 #### *napi_set_named_property*
 <!-- YAML
 added: v8.0.0
@@ -3074,6 +3096,7 @@ support it:
 [`napi_delete_async_work`]: #n_api_napi_delete_async_work
 [`napi_define_class`]: #n_api_napi_define_class
 [`napi_delete_element`]: #n_api_napi_delete_element
+[`napi_delete_property`]: #n_api_napi_delete_property
 [`napi_delete_reference`]: #n_api_napi_delete_reference
 [`napi_escape_handle`]: #n_api_napi_escape_handle
 [`napi_get_array_length`]: #n_api_napi_get_array_length
diff --git a/src/node_api.cc b/src/node_api.cc
index e182459cb8c5f4..ef02bd47f6e549 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -1005,6 +1005,28 @@ 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<v8::Context> context = isolate->GetCurrentContext();
+  v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
+  v8::Local<v8::Object> obj;
+
+  CHECK_TO_OBJECT(env, context, obj, object);
+  v8::Maybe<bool> 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_status napi_set_named_property(napi_env env,
                                     napi_value object,
                                     const char* utf8name,
diff --git a/src/node_api.h b/src/node_api.h
index 58222e74318fe2..503ea258881cad 100644
--- a/src/node_api.h
+++ b/src/node_api.h
@@ -226,6 +226,10 @@ 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_set_named_property(napi_env env,
                                           napi_value object,
                                           const char* utf8name,
diff --git a/test/addons-napi/test_object/test.js b/test/addons-napi/test_object/test.js
index c9c549c827409f..fe6fb169b6c701 100644
--- a/test/addons-napi/test_object/test.js
+++ b/test/addons-napi/test_object/test.js
@@ -120,3 +120,47 @@ assert.strictEqual(newObject.test_string, 'test string');
   assert(wrapper.protoA, true);
   assert(wrapper.protoB, true);
 }
+
+{
+  // Verify that normal and nonexistent properties can be deleted.
+  const sym = Symbol();
+  const obj = { foo: 'bar', [sym]: 'baz' };
+
+  assert.strictEqual('foo' in obj, true);
+  assert.strictEqual(sym in obj, true);
+  assert.strictEqual('does_not_exist' in obj, false);
+  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
+  assert.strictEqual('foo' in obj, false);
+  assert.strictEqual(sym in obj, true);
+  assert.strictEqual('does_not_exist' in obj, false);
+  assert.strictEqual(test_object.Delete(obj, sym), true);
+  assert.strictEqual('foo' in obj, false);
+  assert.strictEqual(sym in obj, false);
+  assert.strictEqual('does_not_exist' in obj, false);
+}
+
+{
+  // Verify that non-configurable properties are not deleted.
+  const obj = {};
+
+  Object.defineProperty(obj, 'foo', { configurable: false });
+  assert.strictEqual(test_object.Delete(obj, 'foo'), false);
+  assert.strictEqual('foo' in obj, true);
+}
+
+{
+  // Verify that prototype properties are not deleted.
+  function Foo() {
+    this.foo = 'bar';
+  }
+
+  Foo.prototype.foo = 'baz';
+
+  const obj = new Foo();
+
+  assert.strictEqual(obj.foo, 'bar');
+  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
+  assert.strictEqual(obj.foo, 'baz');
+  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
+  assert.strictEqual(obj.foo, 'baz');
+}
diff --git a/test/addons-napi/test_object/test_object.c b/test/addons-napi/test_object/test_object.c
index 383fe46342a340..ad24d61ca787e0 100644
--- a/test/addons-napi/test_object/test_object.c
+++ b/test/addons-napi/test_object/test_object.c
@@ -86,6 +86,31 @@ napi_value Has(napi_env env, napi_callback_info info) {
   return ret;
 }
 
+napi_value Delete(napi_env env, napi_callback_info info) {
+  size_t argc = 2;
+  napi_value args[2];
+
+  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
+  NAPI_ASSERT(env, argc == 2, "Wrong number of arguments");
+
+  napi_valuetype valuetype0;
+  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
+  NAPI_ASSERT(env, valuetype0 == napi_object,
+    "Wrong type of arguments. Expects an object as first argument.");
+
+  napi_valuetype valuetype1;
+  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
+  NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol,
+    "Wrong type of arguments. Expects a string or symbol as second.");
+
+  bool result;
+  napi_value ret;
+  NAPI_CALL(env, napi_delete_property(env, args[0], args[1], &result));
+  NAPI_CALL(env, napi_get_boolean(env, result, &ret));
+
+  return ret;
+}
+
 napi_value New(napi_env env, napi_callback_info info) {
   napi_value ret;
   NAPI_CALL(env, napi_create_object(env, &ret));
@@ -171,6 +196,7 @@ void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
     DECLARE_NAPI_PROPERTY("Get", Get),
     DECLARE_NAPI_PROPERTY("Set", Set),
     DECLARE_NAPI_PROPERTY("Has", Has),
+    DECLARE_NAPI_PROPERTY("Delete", Delete),
     DECLARE_NAPI_PROPERTY("New", New),
     DECLARE_NAPI_PROPERTY("Inflate", Inflate),
     DECLARE_NAPI_PROPERTY("Wrap", Wrap),