mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Add initial implementation for SharedArrayBuffer
None of the actual sharing is implemented yet, but this is enough for most basic functionality. Diff Tests: +260 ✅ -262 ❌ +2 💀
This commit is contained in:
parent
1c1aa2c0d0
commit
3781948f0c
Notes:
sideshowbarker
2024-07-17 05:02:35 +09:00
Author: https://github.com/shannonbooth Commit: https://github.com/SerenityOS/serenity/commit/3781948f0c Pull-request: https://github.com/SerenityOS/serenity/pull/19727 Reviewed-by: https://github.com/linusg
13 changed files with 347 additions and 0 deletions
|
@ -196,6 +196,8 @@ set(SOURCES
|
|||
Runtime/ShadowRealmConstructor.cpp
|
||||
Runtime/ShadowRealmPrototype.cpp
|
||||
Runtime/Shape.cpp
|
||||
Runtime/SharedArrayBufferConstructor.cpp
|
||||
Runtime/SharedArrayBufferPrototype.cpp
|
||||
Runtime/StringConstructor.cpp
|
||||
Runtime/StringIterator.cpp
|
||||
Runtime/StringIteratorPrototype.cpp
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
__JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \
|
||||
__JS_ENUMERATE(Set, set, SetPrototype, SetConstructor, void) \
|
||||
__JS_ENUMERATE(ShadowRealm, shadow_realm, ShadowRealmPrototype, ShadowRealmConstructor, void) \
|
||||
__JS_ENUMERATE(SharedArrayBuffer, shared_array_buffer, SharedArrayBufferPrototype, SharedArrayBufferConstructor, void) \
|
||||
__JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \
|
||||
__JS_ENUMERATE(SuppressedError, suppressed_error, SuppressedErrorPrototype, SuppressedErrorConstructor, void) \
|
||||
__JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void) \
|
||||
|
|
|
@ -229,4 +229,21 @@ ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer
|
|||
return new_buffer;
|
||||
}
|
||||
|
||||
// 25.2.1.1 AllocateSharedArrayBuffer ( constructor, byteLength ), https://tc39.es/ecma262/#sec-allocatesharedarraybuffer
|
||||
ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length)
|
||||
{
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]] »).
|
||||
auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr));
|
||||
|
||||
// FIXME: 2. Let block be ? CreateSharedByteDataBlock(byteLength).
|
||||
auto block = TRY(create_byte_data_block(vm, byte_length));
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj->set_buffer(move(block));
|
||||
|
||||
// 5. Return obj.
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& const
|
|||
ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
|
||||
ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM&, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length);
|
||||
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM&, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability);
|
||||
ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
|
||||
|
||||
// 25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/ecma262/#sec-rawbytestonumeric
|
||||
template<typename T>
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
#include <LibJS/Runtime/SetConstructor.h>
|
||||
#include <LibJS/Runtime/ShadowRealmConstructor.h>
|
||||
#include <LibJS/Runtime/Shape.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/StringConstructor.h>
|
||||
#include <LibJS/Runtime/StringPrototype.h>
|
||||
#include <LibJS/Runtime/SuppressedErrorConstructor.h>
|
||||
|
@ -158,6 +159,7 @@ Object& set_default_global_bindings(Realm& realm)
|
|||
global.define_intrinsic_accessor(vm.names.RegExp, attr, [](auto& realm) -> Value { return realm.intrinsics().regexp_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.Set, attr, [](auto& realm) -> Value { return realm.intrinsics().set_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.ShadowRealm, attr, [](auto& realm) -> Value { return realm.intrinsics().shadow_realm_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.SharedArrayBuffer, attr, [](auto& realm) -> Value { return realm.intrinsics().shared_array_buffer_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.String, attr, [](auto& realm) -> Value { return realm.intrinsics().string_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.SuppressedError, attr, [](auto& realm) -> Value { return realm.intrinsics().suppressed_error_constructor(); });
|
||||
global.define_intrinsic_accessor(vm.names.Symbol, attr, [](auto& realm) -> Value { return realm.intrinsics().symbol_constructor(); });
|
||||
|
|
|
@ -90,6 +90,8 @@
|
|||
#include <LibJS/Runtime/ShadowRealmConstructor.h>
|
||||
#include <LibJS/Runtime/ShadowRealmPrototype.h>
|
||||
#include <LibJS/Runtime/Shape.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferPrototype.h>
|
||||
#include <LibJS/Runtime/StringConstructor.h>
|
||||
#include <LibJS/Runtime/StringIteratorPrototype.h>
|
||||
#include <LibJS/Runtime/StringPrototype.h>
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
SharedArrayBufferConstructor::SharedArrayBufferConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.SharedArrayBuffer.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> SharedArrayBufferConstructor::initialize(Realm& realm)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
MUST_OR_THROW_OOM(NativeFunction::initialize(realm));
|
||||
|
||||
// 25.2.4.2 SharedArrayBuffer.prototype, https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().shared_array_buffer_prototype(), 0);
|
||||
|
||||
// 25.2.4.4 SharedArrayBuffer.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.toString
|
||||
define_native_accessor(realm, vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
|
||||
|
||||
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 25.2.2.1 SharedArrayBuffer ( length ), https://tc39.es/ecma262/#sec-sharedarraybuffer-length
|
||||
ThrowCompletionOr<Value> SharedArrayBufferConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.SharedArrayBuffer);
|
||||
}
|
||||
|
||||
// 25.2.2.1 SharedArrayBuffer ( length ), https://tc39.es/ecma262/#sec-sharedarraybuffer-length
|
||||
ThrowCompletionOr<NonnullGCPtr<Object>> SharedArrayBufferConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
auto byte_length_or_error = vm.argument(0).to_index(vm);
|
||||
|
||||
if (byte_length_or_error.is_error()) {
|
||||
auto error = byte_length_or_error.release_error();
|
||||
if (error.value()->is_object() && is<RangeError>(error.value()->as_object())) {
|
||||
// Re-throw more specific RangeError
|
||||
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "shared array buffer");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength).
|
||||
return *TRY(allocate_shared_array_buffer(vm, new_target, byte_length_or_error.release_value()));
|
||||
}
|
||||
|
||||
// 25.2.3.2 get SharedArrayBuffer [ @@species ], https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species
|
||||
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferConstructor::symbol_species_getter)
|
||||
{
|
||||
// 1. Return the this value.
|
||||
return vm.this_value();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class SharedArrayBufferConstructor final : public NativeFunction {
|
||||
JS_OBJECT(SharedArrayBufferConstructor, NativeFunction);
|
||||
|
||||
public:
|
||||
virtual ThrowCompletionOr<void> initialize(Realm&) override;
|
||||
virtual ~SharedArrayBufferConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<NonnullGCPtr<Object>> construct(FunctionObject& new_target) override;
|
||||
|
||||
private:
|
||||
explicit SharedArrayBufferConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
|
||||
};
|
||||
|
||||
}
|
134
Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp
Normal file
134
Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
|
||||
#include <LibJS/Runtime/SharedArrayBufferPrototype.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
SharedArrayBufferPrototype::SharedArrayBufferPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> SharedArrayBufferPrototype::initialize(Realm& realm)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
MUST_OR_THROW_OOM(Base::initialize(realm));
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_accessor(realm, vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
|
||||
define_native_function(realm, vm.names.slice, slice, 2, attr);
|
||||
|
||||
// 25.2.4.4 SharedArrayBuffer.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.toString
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.SharedArrayBuffer.as_string()), Attribute::Configurable);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 25.2.4.1 get SharedArrayBuffer.prototype.byteLength, https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength
|
||||
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::byte_length_getter)
|
||||
{
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
// FIXME: Check for shared buffer
|
||||
|
||||
// 4. Let length be O.[[ArrayBufferByteLength]].
|
||||
// 5. Return 𝔽(length).
|
||||
return Value(array_buffer_object->byte_length());
|
||||
}
|
||||
|
||||
// 25.2.4.3 SharedArrayBuffer.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice
|
||||
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::slice)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
auto start = vm.argument(0);
|
||||
auto end = vm.argument(1);
|
||||
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
|
||||
// FIXME: Check for shared buffer
|
||||
|
||||
// 4. Let len be O.[[ArrayBufferByteLength]].
|
||||
auto length = array_buffer_object->byte_length();
|
||||
|
||||
// 5. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
auto relative_start = TRY(start.to_integer_or_infinity(vm));
|
||||
|
||||
double first;
|
||||
// 6. If relativeStart is -∞, let first be 0.
|
||||
if (Value(relative_start).is_negative_infinity())
|
||||
first = 0;
|
||||
// 7. Else if relativeStart < 0, let first be max(len + relativeStart, 0).
|
||||
else if (relative_start < 0)
|
||||
first = max(length + relative_start, 0.0);
|
||||
// 8. Else, let first be min(relativeStart, len).
|
||||
else
|
||||
first = min(relative_start, (double)length);
|
||||
|
||||
// 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
auto relative_end = end.is_undefined() ? length : TRY(end.to_integer_or_infinity(vm));
|
||||
|
||||
double final;
|
||||
// 10. If relativeEnd is -∞, let final be 0.
|
||||
if (Value(relative_end).is_negative_infinity())
|
||||
final = 0;
|
||||
// 11. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
|
||||
else if (relative_end < 0)
|
||||
final = max(length + relative_end, 0.0);
|
||||
// 12. Else, let final be min(relativeEnd, len).
|
||||
else
|
||||
final = min(relative_end, (double)length);
|
||||
|
||||
// 13. Let newLen be max(final - first, 0).
|
||||
auto new_length = max(final - first, 0.0);
|
||||
|
||||
// 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%).
|
||||
auto* constructor = TRY(species_constructor(vm, array_buffer_object, realm.intrinsics().shared_array_buffer_constructor()));
|
||||
|
||||
// 15. Let new be ? Construct(ctor, « 𝔽(newLen) »).
|
||||
auto new_array_buffer = TRY(construct(vm, *constructor, Value(new_length)));
|
||||
|
||||
// 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
|
||||
if (!is<ArrayBuffer>(new_array_buffer.ptr()))
|
||||
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorDidNotCreate, "an ArrayBuffer");
|
||||
auto* new_array_buffer_object = static_cast<ArrayBuffer*>(new_array_buffer.ptr());
|
||||
|
||||
// 17. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
|
||||
// FIXME: Check for shared buffer
|
||||
|
||||
// 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception.
|
||||
if (new_array_buffer == array_buffer_object)
|
||||
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorReturned, "same ArrayBuffer instance");
|
||||
|
||||
// 19. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
|
||||
if (new_array_buffer_object->byte_length() < new_length)
|
||||
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorReturned, "an ArrayBuffer smaller than requested");
|
||||
|
||||
// 20. Let fromBuf be O.[[ArrayBufferData]].
|
||||
auto& from_buf = array_buffer_object->buffer();
|
||||
|
||||
// 21. Let toBuf be new.[[ArrayBufferData]].
|
||||
auto& to_buf = new_array_buffer_object->buffer();
|
||||
|
||||
// 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first, new_length);
|
||||
|
||||
// 23. Return new.
|
||||
return new_array_buffer_object;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class SharedArrayBufferPrototype final : public PrototypeObject<SharedArrayBufferPrototype, ArrayBuffer> {
|
||||
JS_PROTOTYPE_OBJECT(SharedArrayBufferPrototype, ArrayBuffer, SharedArrayBuffer);
|
||||
|
||||
public:
|
||||
virtual ThrowCompletionOr<void> initialize(Realm&) override;
|
||||
virtual ~SharedArrayBufferPrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit SharedArrayBufferPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(byte_length_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(slice);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
test("basic functionality", () => {
|
||||
expect(SharedArrayBuffer).toHaveLength(1);
|
||||
expect(SharedArrayBuffer.name).toBe("SharedArrayBuffer");
|
||||
expect(SharedArrayBuffer.prototype.constructor).toBe(SharedArrayBuffer);
|
||||
expect(new SharedArrayBuffer()).toBeInstanceOf(SharedArrayBuffer);
|
||||
expect(typeof new SharedArrayBuffer()).toBe("object");
|
||||
});
|
||||
|
||||
test("SharedArrayBuffer constructor must be invoked with 'new'", () => {
|
||||
expect(() => {
|
||||
SharedArrayBuffer();
|
||||
}).toThrowWithMessage(TypeError, "SharedArrayBuffer constructor must be called with 'new'");
|
||||
});
|
||||
|
||||
test("SharedArrayBuffer size limit", () => {
|
||||
expect(() => {
|
||||
new SharedArrayBuffer(2 ** 53);
|
||||
}).toThrowWithMessage(RangeError, "Invalid shared array buffer length");
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
test("basic functionality", () => {
|
||||
expect(new SharedArrayBuffer().byteLength).toBe(0);
|
||||
expect(new SharedArrayBuffer(1).byteLength).toBe(1);
|
||||
expect(new SharedArrayBuffer(64).byteLength).toBe(64);
|
||||
expect(new SharedArrayBuffer(123).byteLength).toBe(123);
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
test("single parameter", () => {
|
||||
const buffer = new SharedArrayBuffer(16);
|
||||
const fullView = new Int32Array(buffer);
|
||||
|
||||
// modify some value that we can check in the sliced buffer
|
||||
fullView[3] = 7;
|
||||
|
||||
// slice the buffer and use a new int32 view to perform basic checks
|
||||
const slicedBuffer = buffer.slice(12);
|
||||
const slicedView = new Int32Array(slicedBuffer);
|
||||
|
||||
expect(slicedView).toHaveLength(1);
|
||||
expect(slicedView[0]).toBe(7);
|
||||
});
|
||||
|
||||
test("both parameters", () => {
|
||||
const buffer = new SharedArrayBuffer(16);
|
||||
const fullView = new Int32Array(buffer);
|
||||
|
||||
// modify some value that we can check in the sliced buffer
|
||||
fullView[1] = 12;
|
||||
|
||||
// slice the buffer and use a new int32 view to perform basic checks
|
||||
const slicedBuffer = buffer.slice(4, 8);
|
||||
const slicedView = new Int32Array(slicedBuffer);
|
||||
|
||||
expect(slicedView).toHaveLength(1);
|
||||
expect(slicedView[0]).toBe(12);
|
||||
});
|
Loading…
Reference in a new issue