From be8ae3aae8308860b58a5163012f7e46f2d3a71b Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Mon, 13 Jan 2020 17:38:47 +0100 Subject: [PATCH] Add iterator close support for for-of statement (#3401) JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- jerry-core/ecma/base/ecma-gc.c | 2 +- jerry-core/vm/vm-stack.c | 10 +- jerry-core/vm/vm-stack.h | 11 +- jerry-core/vm/vm.c | 29 ++- tests/jerry/es2015/for-of-iterator-close.js | 244 ++++++++++++++++++++ 5 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 tests/jerry/es2015/for-of-iterator-close.js diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 8ee3e68c6..ff8c2cfa5 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -815,7 +815,7 @@ ecma_gc_free_executable_object (ecma_object_t *object_p) /**< object */ do { - context_top_p[-1] &= (uint32_t) ~VM_CONTEXT_HAS_LEX_ENV; + context_top_p[-1] &= (uint32_t) ~(VM_CONTEXT_HAS_LEX_ENV | VM_CONTEXT_CLOSE_ITERATOR); uint32_t offsets = vm_get_context_value_offsets (context_top_p); diff --git a/jerry-core/vm/vm-stack.c b/jerry-core/vm/vm-stack.c index 5f0beecd3..d4319a166 100644 --- a/jerry-core/vm/vm-stack.c +++ b/jerry-core/vm/vm-stack.c @@ -18,6 +18,7 @@ #include "ecma-helpers.h" #include "vm-defines.h" #include "vm-stack.h" +#include "ecma-iterator-object.h" /** \addtogroup vm Virtual machine * @{ @@ -79,8 +80,15 @@ vm_stack_context_abort (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ #if ENABLED (JERRY_ES2015) case VM_CONTEXT_FOR_OF: { + ecma_value_t iterator = vm_stack_top_p[-3]; + + if (context_info & VM_CONTEXT_CLOSE_ITERATOR) + { + ecma_op_iterator_close (iterator); + } + ecma_free_value (iterator); + ecma_free_value (vm_stack_top_p[-2]); - ecma_free_value (vm_stack_top_p[-3]); VM_MINUS_EQUAL_U16 (frame_ctx_p->context_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); vm_stack_top_p -= PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION; break; diff --git a/jerry-core/vm/vm-stack.h b/jerry-core/vm/vm-stack.h index cb47177b7..5875c2b89 100644 --- a/jerry-core/vm/vm-stack.h +++ b/jerry-core/vm/vm-stack.h @@ -28,13 +28,13 @@ /** * Create context on the vm stack. */ -#define VM_CREATE_CONTEXT(type, end_offset) ((ecma_value_t) ((type) | ((end_offset) << 6))) +#define VM_CREATE_CONTEXT(type, end_offset) ((ecma_value_t) ((type) | ((end_offset) << 7))) /** * Create context on the vm stack with environment. */ #define VM_CREATE_CONTEXT_WITH_ENV(type, end_offset) \ - ((ecma_value_t) ((type) | ((end_offset) << 6) | VM_CONTEXT_HAS_LEX_ENV)) + (VM_CREATE_CONTEXT ((type),(end_offset)) | VM_CONTEXT_HAS_LEX_ENV) /** * Get type of a vm context. @@ -44,13 +44,18 @@ /** * Get the end position of a vm context. */ -#define VM_GET_CONTEXT_END(value) ((value) >> 6) +#define VM_GET_CONTEXT_END(value) ((value) >> 7) /** * This flag is set if the context has a lexical environment. */ #define VM_CONTEXT_HAS_LEX_ENV 0x20 +/** + * This flag is set if the iterator close operation should be invoked during a for-of context break. + */ +#define VM_CONTEXT_CLOSE_ITERATOR 0x40 + /** * Context types for the vm stack. */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 028af542c..c27a18f16 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -3485,7 +3485,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ VM_PLUS_EQUAL_U16 (frame_ctx_p->context_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); stack_top_p += PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION; - stack_top_p[-1] = VM_CREATE_CONTEXT (VM_CONTEXT_FOR_OF, branch_offset); + stack_top_p[-1] = VM_CREATE_CONTEXT (VM_CONTEXT_FOR_OF, branch_offset) | VM_CONTEXT_CLOSE_ITERATOR; stack_top_p[-2] = next_value; stack_top_p[-3] = iterator; @@ -3587,6 +3587,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ case VM_OC_CONTEXT_END: { JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); + JERRY_ASSERT (!(stack_top_p[-1] & VM_CONTEXT_CLOSE_ITERATOR)); ecma_value_t context_type = VM_GET_CONTEXT_TYPE (stack_top_p[-1]); @@ -3653,6 +3654,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ case VM_OC_JUMP_AND_EXIT_CONTEXT: { JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); + JERRY_ASSERT (!jcontext_has_pending_exception ()); branch_offset += (int32_t) (byte_code_start_p - frame_ctx_p->byte_code_start_p); @@ -3670,6 +3672,14 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ byte_code_p = frame_ctx_p->byte_code_start_p + branch_offset; } +#if ENABLED (JERRY_ES2015) + if (jcontext_has_pending_exception ()) + { + result = ECMA_VALUE_ERROR; + goto error; + } +#endif /* ENABLED (JERRY_ES2015) */ + JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); continue; } @@ -3944,10 +3954,27 @@ error: JERRY_ASSERT (VM_GET_CONTEXT_TYPE (stack_top_p[-1]) == VM_CONTEXT_FINALLY_RETURN); JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); +#if ENABLED (JERRY_ES2015) + if (jcontext_has_pending_exception ()) + { + stack_top_p[-1] = (ecma_value_t) (stack_top_p[-1] - VM_CONTEXT_FINALLY_RETURN + VM_CONTEXT_FINALLY_THROW); + ecma_free_value (result); + result = jcontext_take_exception (); + } +#endif /* ENABLED (JERRY_ES2015) */ + byte_code_p = frame_ctx_p->byte_code_p; stack_top_p[-2] = result; continue; } + +#if ENABLED (JERRY_ES2015) + if (jcontext_has_pending_exception ()) + { + ecma_free_value (result); + result = ECMA_VALUE_ERROR; + } +#endif /* ENABLED (JERRY_ES2015) */ } else if (jcontext_has_pending_exception () && !jcontext_has_pending_abort ()) { diff --git a/tests/jerry/es2015/for-of-iterator-close.js b/tests/jerry/es2015/for-of-iterator-close.js new file mode 100644 index 000000000..54a85932e --- /dev/null +++ b/tests/jerry/es2015/for-of-iterator-close.js @@ -0,0 +1,244 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +function createIterable(arr, methods = {}) { + let iterable = function *() { + let idx = 0; + while (idx < arr.length) { + yield arr[idx]; + idx++; + } + }(); + iterable['return'] = methods['return']; + iterable['throw'] = methods['throw']; + + return iterable; +}; + +function close1() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; return {}; } + }); + for (var it of iter) break; + return closed; +} + +assert(close1()); + +function close2() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; return {}; } + }); + try { + for (var it of iter) throw 0; + assert(false); + } catch(e){ + assert(e === 0); + } + return closed; +} + +assert(close2()); + +function close3() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; return {}; } + }); + for (var it of iter) continue; + return closed; +} + +assert(!close3()); + +function close4() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; throw 6; } + }); + try { + for (var it of iter) throw 5; + assert(false); + } catch(e) { + assert(e === 5); + } + return closed; +} + +assert(close4()); + +function close5() { + var closed_called = 0; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed_called++; throw 6; } + }); + try { + for (var it of iter) { + for (var it of iter) { + throw 5; + } + assert(false); + } + assert(false); + } catch(e) { + assert(e === 5); + } + return closed_called === 2; +} + +assert(close5()); + +function close6() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; return {}; } + }); + for (var it of iter) {}; + + return closed; +} + +assert(!close6()); + +var close7_result = false; +function close7() { + var iter = createIterable([1, 2, 3], { + 'return': function() { close7_result = true; throw "5"; } + }); + + for (var it of iter) { + return "foo"; + } +} + +try { + close7(); + assert(false); +} catch (e) { + assert(close7_result); + assert(e === "5"); +} + +function close8() { + var iter = createIterable([1, 2, 3], { + 'return': function() { close8_result = true; throw "5"; } + }); + + for (var it of iter) { + throw "foo"; + } +} + +var close8_result = false; +try { + close8(); + assert(false); +} catch (e) { + assert(e === "foo"); + assert(close8_result); +} + +function close9() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; throw "5"; } + }); + + try { + for (var it of iter) { + break; + } + } finally { + assert(closed); + throw "foo" + } +} + +try { + close9(); + assert(false); +} catch (e) { + assert(e === "foo"); +} + +function close10() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; return {}; } + }); + + try { + for (var it of iter) { + return "foo"; + } + } finally { + assert(closed); + throw "bar"; + } +} + +try { + close10(); + assert(false); +} catch (e) { + assert(e === "bar"); +} + +function close11() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; throw "5"; } + }); + + try { + for (var it of iter) { + return "foo"; + } + } finally { + assert(closed); + throw "bar"; + } +} + +try { + close11(); + assert(false); +} catch (e) { + assert(e === "bar"); +} + +function close12() { + var closed = false; + var iter = createIterable([1, 2, 3], { + 'return': function() { closed = true; throw "5"; } + }); + + try { + for (var it of iter) { + throw "foo"; + } + } finally { + assert(closed); + throw "bar"; + } +} + +try { + close12(); + assert(false); +} catch (e) { + assert(e === "bar"); +}