From 8772e22ba7d0b1c3b3fb00dd1fc2d94e2d8b5a61 Mon Sep 17 00:00:00 2001 From: "kirill.parshukov" Date: Thu, 19 Oct 2023 11:29:41 +0300 Subject: [PATCH 1/2] stdlib improvements Change-Id: I99494ecc2f3ea2ae693d1a5448201510ec785cd2 Signed-off-by: kirill.parshukov --- plugins/ets/runtime/CMakeLists.txt | 1 + plugins/ets/runtime/ets_libbase_runtime.yaml | 13 ++ .../ets/runtime/intrinsics/escompat_Date.cpp | 11 +- .../intrinsics/std_core_StackTrace.cpp | 92 +++++++++ plugins/ets/stdlib/escompat/Array.ets | 108 ++++------- plugins/ets/stdlib/escompat/ArrayBuffer.ets | 131 ++++++++++++- plugins/ets/stdlib/escompat/Date.ets | 160 ++++++++++++--- plugins/ets/stdlib/escompat/Error.ets | 71 +++++-- plugins/ets/stdlib/std/core/Exception.ets | 40 ++-- plugins/ets/stdlib/std/core/StackTrace.ets | 32 ++- plugins/ets/stdlib/std/core/StdExceptions.ets | 14 +- .../escompat/ArrayBufferTest.ets | 183 ++++++++++++++++++ .../ets_func_tests/escompat/DateTest.ets | 122 ++++++++++++ 13 files changed, 844 insertions(+), 134 deletions(-) create mode 100644 plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp create mode 100644 plugins/ets/tests/ets_func_tests/escompat/ArrayBufferTest.ets create mode 100644 plugins/ets/tests/ets_func_tests/escompat/DateTest.ets diff --git a/plugins/ets/runtime/CMakeLists.txt b/plugins/ets/runtime/CMakeLists.txt index 38e46f8d0..f295c5dd9 100644 --- a/plugins/ets/runtime/CMakeLists.txt +++ b/plugins/ets/runtime/CMakeLists.txt @@ -43,6 +43,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/intrinsics/std_core_gc.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_finalization_queue.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_Promise.cpp + ${ETS_EXT_SOURCES}/intrinsics/std_core_StackTrace.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_Type.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_TypeCreator.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_Method.cpp diff --git a/plugins/ets/runtime/ets_libbase_runtime.yaml b/plugins/ets/runtime/ets_libbase_runtime.yaml index d4eef95b9..4a3d0a033 100644 --- a/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -2805,3 +2805,16 @@ intrinsics: - i64 impl: panda::ets::intrinsics::TypeAPITypeCreatorCtxLambdaTypeAdd clear_flags: [ ] + +####################### +# std.core.StackTrace # +####################### + - name: StdStackTraceProvisionStackTrace + space: ets + class_name: std.core.StackTrace + method_name: provisionStackTrace + static: true + signature: + ret: std.core.StackTraceElement[] + args: [ ] + impl: panda::ets::intrinsics::StdCoreStackTraceProvisionStackTrace diff --git a/plugins/ets/runtime/intrinsics/escompat_Date.cpp b/plugins/ets/runtime/intrinsics/escompat_Date.cpp index 44f764b8d..9503df44f 100644 --- a/plugins/ets/runtime/intrinsics/escompat_Date.cpp +++ b/plugins/ets/runtime/intrinsics/escompat_Date.cpp @@ -34,17 +34,18 @@ extern "C" int64_t EscompatDateNow() extern "C" int64_t EscompatDateGetLocalTimezoneOffset() { time_t now; - struct tm gmt {}; struct tm local {}; - int64_t seconds; std::time(&now); local = *localtime(&now); - gmt = *gmtime(&now); - seconds = static_cast(difftime(mktime(&gmt), mktime(&local))); + std::ostringstream os; + os << std::put_time(&local, "%z"); + std::string s = os.str(); + int hours = std::stoi(s.substr(0, 3), nullptr, 10); + int minutes = std::stoi(s[0] + s.substr(3), nullptr, 10); - return seconds / MINS_IN_HOUR; + return -(hours * MINS_IN_HOUR + minutes); } extern "C" EtsString *EscompatDateGetTimezoneName() diff --git a/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp b/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp new file mode 100644 index 000000000..9130dd7fa --- /dev/null +++ b/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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. + */ + +#include "runtime/runtime_helpers.h" +#include "plugins/ets/runtime/ets_coroutine.h" +#include "plugins/ets/runtime/ets_exceptions.h" +#include "plugins/ets/runtime/ets_panda_file_items.h" +#include "plugins/ets/runtime/ets_vm.h" +#include "plugins/ets/runtime/types/ets_string.h" +#include "runtime/include/thread_scopes.h" + +#include "runtime/include/stack_walker.h" +#include "runtime/include/thread.h" +#include "runtime/interpreter/runtime_interface.h" +#include "runtime/handle_scope.h" +#include "runtime/handle_scope-inl.h" + +namespace panda::ets::intrinsics { + +extern "C" EtsArray *StdCoreStackTraceProvisionStackTrace() +{ + auto coroutine = EtsCoroutine::GetCurrent(); + auto vm = coroutine->GetPandaVM(); + auto linker = vm->GetClassLinker(); + auto stack_trace_element_class = linker->GetClass("Lstd/core/StackTraceElement;"); + auto context = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS); + + auto thread = ManagedThread::GetCurrent(); + auto walker = StackWalker::Create(thread); + + + std::vector lines; + + EtsField *class_name_field = stack_trace_element_class->GetDeclaredFieldIDByName("className"); + EtsField *method_name_field = stack_trace_element_class->GetDeclaredFieldIDByName("methodName"); + EtsField *source_file_name_field = stack_trace_element_class->GetDeclaredFieldIDByName("sourceFileName"); + EtsField *line_number_field = stack_trace_element_class->GetDeclaredFieldIDByName("lineNumber"); + + ASSERT(class_name_field != nullptr); + ASSERT(method_name_field != nullptr); + ASSERT(source_file_name_field != nullptr); + ASSERT(line_number_field != nullptr); + + for (auto stack = StackWalker::Create(thread); stack.HasFrame(); stack.NextFrame()) { + Method *method = stack.GetMethod(); + + const auto &class_name = method->GetClass()->GetName(); + const auto *method_name = method->GetName().data; + const auto *source_file = method->GetClassSourceFile().data; + const auto line_number = method->GetLineNumFromBytecodeOffset(stack.GetBytecodePc()); + + if (method_name == nullptr) { + method_name = utf::CStringAsMutf8(""); + } + + if (source_file == nullptr) { + source_file = utf::CStringAsMutf8(""); + } + + auto current_ets_stack_trace_line = EtsObject::Create(stack_trace_element_class); + + current_ets_stack_trace_line->SetFieldObject(class_name_field, EtsString::CreateFromMUtf8(class_name.c_str())->AsObject()); + current_ets_stack_trace_line->SetFieldObject(method_name_field, EtsString::CreateFromMUtf8(reinterpret_cast(method_name))->AsObject()); + current_ets_stack_trace_line->SetFieldObject(source_file_name_field, EtsString::CreateFromMUtf8(reinterpret_cast(source_file))->AsObject()); + current_ets_stack_trace_line->SetFieldPrimitive(line_number_field, line_number); + + lines.push_back(current_ets_stack_trace_line); + } + + const auto lines_size = static_cast(lines.size()); + auto *result_array = EtsObjectArray::Create(stack_trace_element_class, lines_size); + + for (uint32_t i = 0; i < lines_size; i++) { + result_array->Set(i, lines[static_cast(i)]); + } + + return result_array; +} + +} // namespace panda::ets::intrinsics diff --git a/plugins/ets/stdlib/escompat/Array.ets b/plugins/ets/stdlib/escompat/Array.ets index 2eb37aa10..8c9f05af3 100644 --- a/plugins/ets/stdlib/escompat/Array.ets +++ b/plugins/ets/stdlib/escompat/Array.ets @@ -190,33 +190,9 @@ export final class Array { return this; } - if (start < 0) { - if (start < -this.data.length) { - start = 0; - } else { - start = start + this.data.length; - } - } - - if (end < 0) { - if (end < -this.data.length) { - end = 0; - } else { - end = end + this.data.length; - } - } - - if (end >= this.data.length) { - end = this.data.length; - } - - if (target < 0) { - if (target < -this.data.length) { - target = 0; - } else { - target = target + this.data.length; - } - } + start = this.toNormalizedIndex(start) + end = this.toNormalizedIndex(end) + target = this.toNormalizedIndex(target) if (target <= start) { while (start < end) { @@ -314,25 +290,8 @@ export final class Array { return this; } - if (start < 0) { - if (start < -this.data.length) { - start = 0; - } else { - start = start + this.data.length; - } - } - - if (end < 0) { - if (end < -this.data.length) { - end = 0; - } else { - end = end + this.data.length; - } - } - - if (end >= this.data.length) { - end = this.data.length; - } + start = this.toNormalizedIndex(start) + end = this.toNormalizedIndex(end) if (end < start) { return this; @@ -533,7 +492,7 @@ export final class Array { let sb = new StringBuilder(); for (let i: int = 0; i < this.data.length; i++) { if (i != 0) { - sb.append(sep); + sb.append(sep); } sb.append(this.data[i].toString()); } @@ -1412,14 +1371,14 @@ export final class Array { if (!(other instanceof Array)) { return false; } - let a = other as Array; - if (this.data.length != a.data.length) { + let otherArray = other as Array; + if (this.data.length != otherArray.data.length) { return false; } for (let i = 0; i < this.data.length; i++) { - if (!Array.objEquals(this.data[i], a.data[i])) { - return false + if (!Array.objEquals(this.data[i], otherArray.data[i])) { + return false; } } @@ -1487,7 +1446,7 @@ export final class Array { * @returns shifted element, i.e. that was at index zero */ public shift(): T | null { - if(this.data.length == 0) { + if (this.data.length == 0) { return null } let obj = this.data[0] @@ -1502,7 +1461,7 @@ export final class Array { * @returns removed element */ public pop(): T | null { - if(this.data.length == 0) { + if (this.data.length == 0) { return null } let obj = this.data[this.data.length - 1] @@ -1550,15 +1509,15 @@ export final class Array { * @returns an Array with deleted elements */ public splice(start: int, delete: int): Array { - if(start + delete > this.data.length){ + if (start + delete > this.data.length) { delete = this.data.length - start } let len = this.data.length - delete let arr = new T[len] let ret = new Array() let count: int = 0 - for( let i: int = 0; i < this.data.length; i++){ - if(i >= start && i < start + delete){ + for (let i: int = 0; i < this.data.length; i++){ + if (i >= start && i < start + delete) { ret.push(this.data[i]) continue } @@ -1680,7 +1639,7 @@ export final class Array { * @returns index of first element satisfying to fn, -1 if no such element */ public findLastIndex(fn: (element : T) => boolean): int { - for(let i = this.data.length - 1; i >= 0; i--) { + for (let i = this.data.length - 1; i >= 0; i--) { if (fn(this.data[i])) { return i } @@ -1731,7 +1690,7 @@ export final class Array { * @returns true is arr is a non-null array, false otherwise */ public static isArray(arr: T[]): boolean { - if(arr == null){ + if (arr == null) { return false } return true @@ -1745,7 +1704,7 @@ export final class Array { * @returns true is arr is a non-null and non-empty array, false otherwise */ public static isArray(arr: Array): boolean { - if(arr.getData() == null){ + if (arr.getData() == null) { return false } return true @@ -1760,7 +1719,7 @@ export final class Array { public toReversed(): Array { let arr = new T[this.data.length] let count = this.data.length - 1 - for(let i = 0; i < this.data.length; i++) { + for (let i = 0; i < this.data.length; i++) { arr[count] = this.data[i] count-- } @@ -1781,7 +1740,7 @@ export final class Array { */ public reduce(fn: (a: U, b: T) => U, initVal: U): U { let acc: U = initVal - for(let i = 0; i < this.data.length; i++) { + for (let i = 0; i < this.data.length; i++) { acc = fn(acc, this.data[i]) } return acc @@ -1822,7 +1781,7 @@ export final class Array { */ public reduceRight(fn: (a: U, b: T) => U, initVal: U): U { let acc: U = initVal - for(let i = this.data.length - 1; i >= 0; i--) { + for (let i = this.data.length - 1; i >= 0; i--) { acc = fn(acc, this.data[i]) } return acc @@ -1843,7 +1802,7 @@ export final class Array { throw new TypeError("Reduce of empty array with no initial value") } let acc: T = this.data[this.data.length - 1] - for(let i = this.data.length - 2; i >= 0; i--) { + for (let i = this.data.length - 2; i >= 0; i--) { acc = fn(acc, this.data[i]) } return acc @@ -1855,7 +1814,7 @@ export final class Array { * @param fn to apply for each element of the Array */ public forEach(fn: (a: T) => void): void { - for(let i = 0; i < this.data.length; i++) { + for (let i = 0; i < this.data.length; i++) { fn(this.data[i]) } } @@ -1919,7 +1878,7 @@ export final class Array { */ public with(index: int, value: T): Array { let arr = new T[this.data.length] - for(let i: int = 0; i < this.data.length; i++) { + for (let i: int = 0; i < this.data.length; i++) { arr[i] = this.data[i] } arr[index] = value @@ -2032,6 +1991,25 @@ export final class Array { return new ValuesIterator(this.data); } + /** + * Returns index in [0; len] + * + * @param index value to be normalized + */ + private toNormalizedIndex(index: int): int { + if (index < 0) { + if (index < -this.data.length) { + return 0; + } else { + return index + this.data.length; + } + } + else if (index > this.data.length) { + return this.data.length; + } + return index; + } + /** * Returns an array iterator */ diff --git a/plugins/ets/stdlib/escompat/ArrayBuffer.ets b/plugins/ets/stdlib/escompat/ArrayBuffer.ets index 036663e16..aab49c986 100644 --- a/plugins/ets/stdlib/escompat/ArrayBuffer.ets +++ b/plugins/ets/stdlib/escompat/ArrayBuffer.ets @@ -33,14 +33,24 @@ package escompat; export class ArrayBuffer extends Buffer { /** - * Creates ArrayBuffer with size equal to length parameter + * Creates non-resizable ArrayBuffer with size equal to length parameter * * @param length size of ArrayBuffer */ - public constructor(length: int) - { - this.data = new byte[length] - this.byteLength = length + public constructor(length: int) { + this.init(length, length, false) + } + + /** + * Creates resizable ArrayBuffer with size equal to length parameter and maximum size equal to maxLength parameter. + * No such constructor in JS library, needed for resizable array buffers + * + * @param length size of ArrayBuffer + * + * @param maxlength maximum size of ArrayBuffer + */ + public constructor(length: int, maxLength: int) { + this.init(length, maxLength, true) } /** @@ -179,6 +189,15 @@ export class ArrayBuffer extends Buffer */ //TODO: return undefined public resize(newLen : int): void { + if (!this._resizable) { + throw new TypeError("ArrayBuffer.resize called on non-resizable ArrayBuffer") + } + if (newLen > this._maxByteLength) { + throw new RangeError("ArrayBuffer.resize: given new length is greater than maxByteLength = " + this._maxByteLength) + } + if (newLen < 0) { + throw new RangeError("ArrayBuffer.resize: invalid newLen parameter") + } let newData = new byte[newLen] let size = min(newLen, this.data.length) for (let i = 0; i < size; ++i) { @@ -189,9 +208,107 @@ export class ArrayBuffer extends Buffer public static native from(array: Object): ArrayBuffer; + /** + * Transfers data to new ArrayBuffer + */ + public transfer(): ArrayBuffer { + return this.transfer(this.byteLength, false) + } + + /** + * Transfers data to new ArrayBuffer + * + * @param bytesCount how many bytes from this ArrayBuffer will be transfered to new ArrayBuffer + */ + public transfer(bytesCount: int): ArrayBuffer { + return this.transfer(bytesCount, false) + } + + /** + * Transfers data to new non-resizable ArrayBuffer + */ + public transferToFixedLength(): ArrayBuffer { + return this.transferToFixedLength(this.byteLength) + } + + /** + * Transfers data to new non-resizable ArrayBuffer + * + * @param bytesCount how many bytes from this ArrayBuffer will be transfered to new ArrayBuffer + */ + public transferToFixedLength(bytesCount: int): ArrayBuffer { + let newBuffer = this.transfer(bytesCount, true) + newBuffer._resizable = false + newBuffer._maxByteLength = newBuffer.byteLength + return newBuffer + } + + /** + * Returns size on data in bytes + */ + get byteLength(): int { + return this.data.length + } + + /** + * Returns max size on data in bytes + */ + get maxByteLength(): int { + return this._maxByteLength + } + + /** + * Returns indicator of whether the size can be changed in future + */ + get resizable(): boolean { + return this._resizable + } + + /** + * Transfers data to new ArrayBuffer + * + * @param bytesCount how many bytes from this ArrayBuffer will be transfered to new ArrayBuffer + * + * @param toFixed disables resizing of new ArrayBuffer if true + */ + private transfer(bytesCount: int, toFixed: boolean): ArrayBuffer { + if (!toFixed && this.resizable && bytesCount > this._maxByteLength) { + throw new RangeError("ArrayBuffer.transfer: can't transfer to resizable ArrayBuffer with greater length") + } + let maxSize = bytesCount + if (this.resizable == true) { + maxSize = max(maxSize, this._maxByteLength) + } + let newBuffer = new ArrayBuffer(bytesCount, maxSize) + for (let i: int = 0; i < min(bytesCount, this.data.length); i++) { + newBuffer.data[i] = this.data[i] + } + newBuffer._resizable = this._resizable + this.data = [] + this._maxByteLength = 0 + return newBuffer + } + + /** + * Creates data array and sets necessary fields + * + * @param length size of data in bytes + * + * @param maxLength max size of buffer in bytes + * + * @param resizable indicator of whether the size can be changed in future + */ + private init(length: int, maxLength: int, resizable: boolean): void { + this.data = new byte[length] + this._maxByteLength = maxLength + this._resizable = resizable + } + private data: byte[] - /** Length in bytes */ - public readonly byteLength: int + /** Max length in bytes */ + private _maxByteLength: int + /** Indicates whether the size can be changed */ + private _resizable: boolean } /** diff --git a/plugins/ets/stdlib/escompat/Date.ets b/plugins/ets/stdlib/escompat/Date.ets index 26cc068ca..280b26951 100644 --- a/plugins/ets/stdlib/escompat/Date.ets +++ b/plugins/ets/stdlib/escompat/Date.ets @@ -47,6 +47,12 @@ const dayCountInYear: int = 365; /** Days in a leap year */ const dayCountInLeapYear: int = 366; +/** Months in a year */ +const monthCountPerYear: int = 12; + +/** Max possible count of days in month */ +const maxDaysInMonth = 31; + /** * This gives a range of 8,640,000,000,000,000 milliseconds * to either side of 01 January, 1970 UTC. @@ -418,30 +424,140 @@ export final class Date { * If the argument doesn't represent a valid date, `InvalidDate` exception is thrown. */ private static parseDateFromString(dateStr: String): long throws { + const dateStrLen = dateStr.length(); + if (dateStrLen < 4) { + return 0; + } + if (dateStrLen > 35) { + throw new InvalidDate("too many symbols in date"); + } + let date = new Date(); - let sub = dateStr.substring(0, 4); - let val = Double.parseInt(sub, 10) as int; - date.setUTCFullYear(val); - sub = dateStr.substring(5, 7); - val = Double.parseInt(sub, 10) as int; - date.setUTCMonth(val); - sub = dateStr.substring(8, 10); - val = Double.parseInt(sub, 10) as int; - date.setUTCDate(val as byte); - sub = dateStr.substring(11, 13); - val = Double.parseInt(sub, 10) as int; - date.setUTCHours(val as byte); - sub = dateStr.substring(14, 16); - val = Double.parseInt(sub, 10) as int; - date.setUTCMinutes(val as byte); - sub = dateStr.substring(17, 19); - val = Double.parseInt(sub, 10) as int; - date.setUTCSeconds(val as byte); - sub = dateStr.substring(20, 23); - val = Double.parseInt(sub, 10) as int; - date.setUTCMilliseconds(val as byte); - return date.valueOf(); + let timeZone = date.TZOffset * msPerSecond; + if (dateStrLen < 5) { + timeZone = 0; + } + + const years = Date.parseIfContains(dateStr, 0, 4); + const months = Date.parseIfContains(dateStr, 5, 7); + const days = Date.parseIfContains(dateStr, 8, 10); + const hours = Date.parseIfContains(dateStr, 11, 13); + const minutes = Date.parseIfContains(dateStr, 14, 16); + const seconds = Date.parseIfContains(dateStr, 17, 19); + const milliseconds = Date.parseIfContains(dateStr, 20, 23); + + date.setUTCFullYear(years); + if (months != Date.UNSUCCESSFUL_PARSING) { + if (months > monthCountPerYear || months <= 0) { + throw new InvalidDate("Invalid month"); + } + date.setUTCMonth((months - 1) as int); + } else { + date.setUTCMonth(0 as int); + } + if (days != Date.UNSUCCESSFUL_PARSING) { + if (days > maxDaysInMonth || days <= 0) { + throw new InvalidDate("Invalid day"); + } + date.setUTCDate((days - 1) as byte); + } else { + date.setUTCDate(0 as byte); + } + if (hours != Date.UNSUCCESSFUL_PARSING) { + if (hours > hoursPerDay || hours < 0) { + throw new InvalidDate("Invalid hours"); + } + date.setUTCHours(hours as byte); + } else { + date.setUTCHours(0 as byte); + } + if (minutes != Date.UNSUCCESSFUL_PARSING) { + if (minutes >= minutesPerHour || minutes < 0) { + throw new InvalidDate("Invalid minutes"); + } + date.setUTCMinutes(minutes as byte); + } else { + date.setUTCMinutes(0 as byte); + } + if (seconds != Date.UNSUCCESSFUL_PARSING) { + if (seconds >= secondsPerMinute || seconds < 0) { + throw new InvalidDate("Invalid seconds"); + } + date.setUTCSeconds(seconds as byte); + } else { + date.setUTCSeconds(0 as byte); + } + if (milliseconds != Date.UNSUCCESSFUL_PARSING) { + if (milliseconds >= msPerSecond || milliseconds < 0) { + throw new InvalidDate("Invalid millis"); + } + date.setUTCMilliseconds(milliseconds as short); + } else { + date.setUTCMilliseconds(0 as short); + } + if (dateStrLen > 23) { + timeZone = 0; + let timeZoneSign = 0; + let timeZoneSignChar = dateStr.at(23); + if (timeZoneSignChar == c'-') { + timeZoneSign = -1; + } else if (timeZoneSignChar == c'+') { + timeZoneSign = 1; + } else { + throw new InvalidDate("Invalid timezone sign"); + } + const timeZoneHours = Date.parseIfContains(dateStr, 24, 26); + const timeZoneMinutes = Date.parseIfContains(dateStr, 27, 29); + const timeZoneSeconds = Date.parseIfContains(dateStr, 30, 32); + const timeZoneSecondsFraction = Date.parseIfContains(dateStr, 33, min(36, dateStrLen)); + if (timeZoneHours != Date.UNSUCCESSFUL_PARSING) { + if (timeZoneHours < 0 || timeZoneHours >= hoursPerDay) { + throw new InvalidDate("Invalid timezone hours"); + } + timeZone += timeZoneHours * msPerHour; + } + if (timeZoneMinutes != Date.UNSUCCESSFUL_PARSING) { + if (timeZoneMinutes < 0 || timeZoneMinutes >= minutesPerHour) { + throw new InvalidDate("Invalid timezone minutes"); + } + timeZone += timeZoneMinutes * msPerMinute; + } + if (timeZoneSeconds != Date.UNSUCCESSFUL_PARSING) { + if (timeZoneSeconds < 0 || timeZoneSeconds >= secondsPerMinute) { + throw new InvalidDate("Invalid timezone seconds"); + } + timeZone += timeZoneSeconds * msPerSecond; + } + if (timeZoneSecondsFraction != Date.UNSUCCESSFUL_PARSING) { + let timeZoneMillis = timeZoneSecondsFraction; + if (dateStrLen == 32) { + timeZoneMillis *= 100; + } else if (dateStrLen == 33) { + timeZoneMillis *= 10; + } + if (timeZoneSecondsFraction < 0) { + throw new InvalidDate("Invalid timezone seconds fraction"); + } + timeZone += timeZoneSecondsFraction; + } + + timeZone *= timeZoneSign; + } + return date.valueOf() - timeZone; } + + /** + * Parses int from substring if indexes are in string + */ + private static parseIfContains(dateStr: String, begin: int, end: int): int throws { + if (dateStr.length() >= end && begin < end) { + return Double.parseInt(dateStr.substring(begin, end)) as int; + } + return Date.UNSUCCESSFUL_PARSING; + } + + private static readonly UNSUCCESSFUL_PARSING: int = -1; + /** * Default constructor. * diff --git a/plugins/ets/stdlib/escompat/Error.ets b/plugins/ets/stdlib/escompat/Error.ets index 49e8cfec5..0b193c1e2 100644 --- a/plugins/ets/stdlib/escompat/Error.ets +++ b/plugins/ets/stdlib/escompat/Error.ets @@ -20,7 +20,6 @@ package escompat; * Serves as a base class for all error classes. */ export class Error { - public stack: String[]; public cause: Object; public message: String; public name: String; @@ -29,10 +28,6 @@ export class Error { public lineNumber: int; public columnNumber: int; - private provisionStackTrace(): void { - this.stack = stackTraceLines(); - } - /** * Constructs a new empty error instance */ @@ -55,7 +50,7 @@ export class Error { this.message = msg; this.cause = this; this.name = "Error"; - this.provisionStackTrace() + this.provisionStackTrace(); } /** @@ -79,20 +74,26 @@ export class Error { * @returns result of the conversion */ override toString(): String { - let s: String = ""; - if (this.message != "") { - s += this.name + ": " + this.message + "\n"; + return this.name + ": " + this.message + "\n"; } + return this.name; + } - for (let i: int = (this.stack.length > 2 ? 2 : 0); i < this.stack.length; i++) { - s += this.stack[i]; - if (i != this.stack.length - 1) { - s += "\n"; - } - } + /** + * Forms stack and returns it + */ + get stack(): String { + this.formStack(); + return this._stack; + } - return s; + /** + * Cleans up stack lines + */ + set stack(newStack: String) { + this._stack = newStack; + this.stackLines = [] } // TODO(nsizov): Uncomment and rewrite when proper getters and setters will be implemented @@ -120,4 +121,42 @@ export class Error { // public getMessage(): String { // return this.msg; // } + + /** + * Forms stack from this.stackLines and stores it in this._stack + */ + private formStack() { + if (this._stack != null && !this._stack.isEmpty()) { + return; + } + if (this.stackLines == null || this.stackLines.length == 0) { + return + } + let builder = new StringBuilder(""); + + if (this.message != "") { + let val = this.name + ": " + this.message + "\n"; + builder.append(val); + } + + // TODO: find a better way to erase Error's ctors lines + const provisionStackTraceLevel = 2; + const realStackStart = (this.stackLines.length > provisionStackTraceLevel ? provisionStackTraceLevel : 0); + for (let i: int = realStackStart; i < this.stackLines.length; i++) { + builder.append(this.stackLines[i].toString() + '\n'); + } + + this._stack = builder.toString(); + this.stackLines = []; + } + + /** + * Gets stack trace from native and stores it in this.stackLines + */ + private provisionStackTrace(): void { + this.stackLines = StackTrace.provisionStackTrace(); + } + + private stackLines: StackTraceElement[]; + private _stack: String; } diff --git a/plugins/ets/stdlib/std/core/Exception.ets b/plugins/ets/stdlib/std/core/Exception.ets index 552e8587a..2a344511e 100644 --- a/plugins/ets/stdlib/std/core/Exception.ets +++ b/plugins/ets/stdlib/std/core/Exception.ets @@ -20,13 +20,6 @@ package std.core; * Serves as a base class for all exception classes. */ export class Exception { - private stackLines: String[]; - private cause: Object; - private msg: String; - private provisionStackTrace(): void { - this.stackLines = stackTraceLines(); - } - /** * Constructs a new empty exception instance */ @@ -67,20 +60,24 @@ export class Exception { * @returns result of the conversion */ override toString(): String { - let s: String = ""; + let builder = new StringBuilder(""); + + if (!this.msg.isEmpty()) { + builder.append(this.msg + '\n'); + } - if (this.msg != "") { - s += this.msg + "\n"; + if (this.stackLines == null || this.stackLines.length == 0) { + return builder.toString() } - for (let i: int = (this.stackLines.length > 2 ? 2 : 0); i < this.stackLines.length; i++) { - s += this.stackLines[i]; - if (i != this.stackLines.length-1) { - s += "\n"; - } + // TODO: find a better way to erase Error's ctors lines + const provisionStackTraceLevel = 2; + const realStackStart = (this.stackLines.length > provisionStackTraceLevel ? provisionStackTraceLevel : 0); + for (let i: int = realStackStart; i < this.stackLines.length; i++) { + builder.append(this.stackLines[i].toString() + '\n'); } - return s; + return builder.toString(); } /** @@ -95,4 +92,15 @@ export class Exception { return this.cause; } } + + private stackLines: StackTraceElement[]; + private cause: Object; + private msg: String; + + /** + * Gets stack from native and stores it in this.stackLines + */ + private provisionStackTrace(): void { + this.stackLines = StackTrace.provisionStackTrace(); + } } diff --git a/plugins/ets/stdlib/std/core/StackTrace.ets b/plugins/ets/stdlib/std/core/StackTrace.ets index c7d3d8684..efcc46532 100644 --- a/plugins/ets/stdlib/std/core/StackTrace.ets +++ b/plugins/ets/stdlib/std/core/StackTrace.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * 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 @@ -16,7 +16,35 @@ package std.core; export class StackTraceElement { - // ... + public className: String; + public methodName: String; + public sourceFileName: String; + public lineNumber: int; + + /** + * Packs data to single string and + * + * @returns data in formatted string + */ + public override toString(): String { + return this.generateString(); + } + + /** + * Packs data to single string and + * + * @returns data in formatted string + */ + protected generateString(): String { + if (this.resultString == null || this.resultString.isEmpty()) { + this.resultString = this.className + '.' + + this.methodName + ' at ' + this.sourceFileName + + ':' + this.lineNumber; + } + return this.resultString; + } + + protected resultString: String; } export class StackTrace { diff --git a/plugins/ets/stdlib/std/core/StdExceptions.ets b/plugins/ets/stdlib/std/core/StdExceptions.ets index 2037a894d..8dd252081 100644 --- a/plugins/ets/stdlib/std/core/StdExceptions.ets +++ b/plugins/ets/stdlib/std/core/StdExceptions.ets @@ -276,4 +276,16 @@ export final class IllegalArgumentException extends Exception { /** * Represents exception that is thrown when Date is malformed */ -export final class InvalidDate extends Exception {} +export final class InvalidDate extends Exception { + constructor() { + super(); + } + + constructor(s: String) { + super(s); + } + + constructor(s: String, cause: Object) { + super(s, cause); + } +} diff --git a/plugins/ets/tests/ets_func_tests/escompat/ArrayBufferTest.ets b/plugins/ets/tests/ets_func_tests/escompat/ArrayBufferTest.ets new file mode 100644 index 000000000..fee423a8b --- /dev/null +++ b/plugins/ets/tests/ets_func_tests/escompat/ArrayBufferTest.ets @@ -0,0 +1,183 @@ +function setValueInBuffer(buff: ArrayBuffer, where: int, what: byte): void { + let dataView = new DataView(buff) + dataView.setInt8(where, what) +} + +function getValueInBuffer(buff: ArrayBuffer, where: int): byte { + let dataView = new DataView(buff) + return dataView.getInt8(where) +} + +function testTransferNonResizable(): int { + const startBufferLength = 16 + const middleBufferLength = 8 + const endBufferLength = 32 + const valuePos = 2 + const value = 2 as byte + let buff = new ArrayBuffer(startBufferLength) + setValueInBuffer(buff, valuePos, value) + + let buff2 = buff.transfer(middleBufferLength) + if (buff.byteLength != 0 || buff.resizable || buff.maxByteLength != 0) { + console.println("testTransferNonResizable failed: incorrect original buffer state after transfering") + return 1 + } else if (buff2.byteLength != middleBufferLength || buff2.maxByteLength != middleBufferLength || buff2.resizable) { + console.println("testTransferNonResizable failed: incorrect middle buffer state") + return 1 + } + + let buff3 = buff2.transfer(endBufferLength) + if (buff2.byteLength != 0 || buff2.resizable || buff2.maxByteLength != 0) { + console.println("testTransferNonResizable failed: incorrect middle buffer state after transfering") + return 1 + } else if (buff3.byteLength != endBufferLength || buff3.maxByteLength != endBufferLength || buff3.resizable) { + console.println("testTransferNonResizable failed: incorrect end buffer state") + return 1 + } + if (getValueInBuffer(buff3, valuePos) != 2) { + console.println("testTransferNonResizable failed: incorrect data transfering") + return 1 + } + + return 0 +} + +function testTransferResizable(): int { + const startBufferLength = 16 + const startBufferMaxLength = 32 + const middleBufferLength = 8 + const endBufferLength = 32 + const valuePos = 2 + const value = 2 as byte + let buff = new ArrayBuffer(startBufferLength, startBufferMaxLength) + setValueInBuffer(buff, valuePos, value) + + let buff2 = buff.transfer(middleBufferLength) + if (buff.byteLength != 0 || buff.resizable == false || buff.maxByteLength != 0) { + console.println("testTransferResizable failed: incorrect original buffer state after transfering") + return 1 + } else if (buff2.byteLength != middleBufferLength || buff2.maxByteLength != startBufferMaxLength || buff2.resizable == false) { + console.println("testTransferResizable failed: incorrect middle buffer state") + return 1 + } + + let buff3 = buff2.transfer(endBufferLength) + if (buff2.byteLength != 0 || buff2.resizable == false || buff2.maxByteLength != 0) { + console.println("testTransferResizable failed: incorrect middle buffer state after transfering") + return 1 + } else if (buff3.byteLength != endBufferLength || buff3.maxByteLength != startBufferMaxLength || buff3.resizable == false) { + console.println("testTransferResizable failed: incorrect end buffer state") + return 1 + } + if (getValueInBuffer(buff3, valuePos) != 2) { + console.println("testTransferResizable failed: incorrect data transfering") + return 1 + } + + let catchedError = false + try { + buff3.transfer(startBufferMaxLength * 2) + } catch (e) { + catchedError = true + } + if (!catchedError) { + console.println("testTransferResizable failed: it is possible to transfer to ArrayBuffer with greater max length") + return 1 + } + + return 0 +} + +function testTransferToFixedLengthNonResizable(): int { + const startBufferLength = 16 + const middleBufferLength = 8 + const endBufferLength = 32 + const valuePos = 2 + const value = 2 as byte + let buff = new ArrayBuffer(startBufferLength) + setValueInBuffer(buff, valuePos, value) + + let buff2 = buff.transferToFixedLength(middleBufferLength) + if (buff.byteLength != 0 || buff.resizable || buff.maxByteLength != 0) { + console.println("testTransferNonResizable failed: incorrect original buffer state after transfering") + return 1 + } else if (buff2.byteLength != middleBufferLength || buff2.maxByteLength != middleBufferLength || buff2.resizable) { + console.println("testTransferNonResizable failed: incorrect middle buffer state") + return 1 + } + + let buff3 = buff2.transferToFixedLength(endBufferLength) + if (buff2.byteLength != 0 || buff2.resizable || buff2.maxByteLength != 0) { + console.println("testTransferNonResizable failed: incorrect middle buffer state after transfering") + return 1 + } else if (buff3.byteLength != endBufferLength || buff3.maxByteLength != endBufferLength || buff3.resizable) { + console.println("testTransferNonResizable failed: incorrect end buffer state") + return 1 + } + if (getValueInBuffer(buff3, valuePos) != 2) { + console.println("testTransferNonResizable failed: incorrect data transfering") + return 1 + } + + return 0 +} + +function testTransferToFixedLengthResizable(): int { + const startBufferLength = 16 + const startBufferMaxLength = 32 + const middleBufferLength = 8 + const endBufferLength = 32 + const valuePos = 2 + const value = 2 as byte + let buff = new ArrayBuffer(startBufferLength, startBufferMaxLength) + setValueInBuffer(buff, valuePos, value) + + let buff2 = buff.transferToFixedLength(middleBufferLength) + if (buff.byteLength != 0 || buff.resizable == false || buff.maxByteLength != 0) { + console.println("testTransferResizable failed: incorrect original buffer state after transfering") + return 1 + } else if (buff2.byteLength != middleBufferLength || buff2.maxByteLength != middleBufferLength || buff2.resizable) { + console.println("testTransferResizable failed: incorrect middle buffer state") + return 1 + } + + let buff3 = buff2.transferToFixedLength(endBufferLength) + if (buff2.byteLength != 0 || buff2.resizable || buff2.maxByteLength != 0) { + console.println("testTransferResizable failed: incorrect middle buffer state after transfering") + return 1 + } else if (buff3.byteLength != endBufferLength || buff3.maxByteLength != endBufferLength || buff3.resizable) { + console.println("testTransferResizable failed: incorrect end buffer state") + return 1 + } + if (getValueInBuffer(buff3, valuePos) != 2) { + console.println("testTransferResizable failed: incorrect data transfering") + return 1 + } + + let catchedError = false + try { + buff3.transferToFixedLength(startBufferMaxLength * 2) + } catch (e) { + catchedError = true + } + if (catchedError) { + console.println("testTransferResizable failed: it is impossible to transfer to ArrayBuffer with greater max length") + return 1 + } + + return 0 +} + +type testFuncType = () => int + +function main(): int { + console.println("Started ArrayBuffer tests") + let failures = 0 + let tests: testFuncType[] = [ testTransferNonResizable, testTransferResizable, testTransferToFixedLengthNonResizable, testTransferToFixedLengthResizable ] + const testsCount = tests.length + for (let test of tests) { + failures += test() + } + console.println("PASSED: " + (testsCount - failures) + " out of " + testsCount + " tests!") + return failures +} diff --git a/plugins/ets/tests/ets_func_tests/escompat/DateTest.ets b/plugins/ets/tests/ets_func_tests/escompat/DateTest.ets new file mode 100644 index 000000000..13e912034 --- /dev/null +++ b/plugins/ets/tests/ets_func_tests/escompat/DateTest.ets @@ -0,0 +1,122 @@ +function runTimeZoneDependentParseTests(): int { + let fails = 0; + let tzOffset = new Date().getTimezoneOffset() * 1000; + let tests: String[] = [ + //"1970 02 01 00:00:00.000", TODO: it seems like setUTCMonth reads '1' and '2' as the same month (March) uncomment and check it later + "1970 01 02 00:00:00.000", + "2044 01 28 23:00:00.001", + "2033 01 21 22:40:40", + "2176 03 26 20:00", + "3099 04 25", + "9999 05", + "2099 06 23 14:00:00.001", + "4999 07 22 12:00:00.001", + "5000 08 21 10:00:00.001", + "5001 09 20 08:00:00.001", + "9998 10 08 04:00:00.001", + "9998 11 15 00:00:00.001", + "9998 12 01 01:59:00.001" + ]; + let answers: long[] = [ + //2678400000, TODO: it seems like setUTCMonth reads '1' and '2' as the same month (March) uncomment and check it later + 86400000, + 2337634800001, + 1989960040000, + 6508152000000, + 35637667200000, + 253381132800000, + 4085906400001, + 95603544000001, + 95637664800001, + 95671785600001, + 253363435200001, + 253366704000001, + 253368093540001 + ]; + for (let i = 0; i < tests.length; i++) { + try { + let parsingRes = Date.parse(tests[i]) + tzOffset; + if (parsingRes != answers[i]) { + console.println("Bad parsing of " + tests[i]); + console.println("result is " + parsingRes); + console.println("correct answer is " + answers[i]); + console.println("diff: " + (parsingRes - answers[i])); + } + } catch(e) { + console.log(e.toString()); + console.println('Unable to parse ' + tests[i]); + fails++; + } + } + return fails; +} + +function runTimeZoneIndependentParseTests(): int { + let fails = 0; + let tests: String[] = [ + "1970", + "2370", + "2044 01 28 23:00:00.001+01:15", + "2099 06 23 14:00:00.001+02:15", + "4999 07 22 12:00:00.001+03:15", + "5000 08 21 10:02:00.001+12:15", + "5001 09 20 08:00:22.301+23:15", + "9998 10 08 04:00:00.004-00:15", + "9998 11 15 00:00:00.001-12:15", + "9998 12 01 01:59:00.001-23:15" + ]; + let answers: long[] = [ + 0, + 12622780800000, + 2337630300001, + 4085898300001, + 95603532300001, + 95637620820001, + 95671701922301, + 253363436100004, + 253366748100001, + 253368177240001 + ]; + for (let i = 0; i < tests.length; i++) { + try { + let parsingRes = Date.parse(tests[i]); + if (parsingRes != answers[i]) { + console.println("Bad parsing of " + tests[i]); + console.println("result is " + parsingRes); + console.println("correct answer is " + answers[i]); + console.println("diff: " + (parsingRes - answers[i])); + } + } catch(e) { + console.log(e.toString()); + console.println('Unable to parse ' + tests[i]); + fails++; + } + } + return fails; +} + +function runParseTests(): int { + return runTimeZoneDependentParseTests() + runTimeZoneIndependentParseTests(); +} + +function main(): int { + /* TODO: it seems like setUTCMonth reads '1' and '2' as the same month (March) uncomment and check it later + try { + let date = new Date(); + date.setUTCFullYear(1970); + date.setUTCMonth(1); + date.setUTCDay(0 as byte); + date.setUTCHours(0 as byte); + date.setUTCMinutes(0 as byte); + date.setUTCSeconds(0 as byte); + date.setUTCMilliseconds(0 as byte); + console.log(date.valueOf()); + } catch (e) { + console.log(" ----- ") + } */ + console.println('Running Date tests'); + let fails = 0; + fails += runParseTests(); + return fails; +} + -- Gitee From 8d1c9c4e2068ec4e0ad414a0bd6f2c217ba7f326 Mon Sep 17 00:00:00 2001 From: "kirill.parshukov" Date: Thu, 2 Nov 2023 12:53:09 +0300 Subject: [PATCH 2/2] fix clang-tidy issues Change-Id: Ic84d2bb06cb894205fd40c9b91f8671b6afaea9b Signed-off-by: kirill.parshukov --- plugins/ets/runtime/intrinsics/escompat_Date.cpp | 5 +++-- plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ets/runtime/intrinsics/escompat_Date.cpp b/plugins/ets/runtime/intrinsics/escompat_Date.cpp index 9503df44f..86a2f0c24 100644 --- a/plugins/ets/runtime/intrinsics/escompat_Date.cpp +++ b/plugins/ets/runtime/intrinsics/escompat_Date.cpp @@ -42,8 +42,9 @@ extern "C" int64_t EscompatDateGetLocalTimezoneOffset() std::ostringstream os; os << std::put_time(&local, "%z"); std::string s = os.str(); - int hours = std::stoi(s.substr(0, 3), nullptr, 10); - int minutes = std::stoi(s[0] + s.substr(3), nullptr, 10); + const int base = 10; + int hours = std::stoi(s.substr(0, 3), nullptr, base); + int minutes = std::stoi(s[0] + s.substr(3), nullptr, base); return -(hours * MINS_IN_HOUR + minutes); } diff --git a/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp b/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp index 9130dd7fa..d88392ac8 100644 --- a/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp +++ b/plugins/ets/runtime/intrinsics/std_core_StackTrace.cpp @@ -35,7 +35,6 @@ extern "C" EtsArray *StdCoreStackTraceProvisionStackTrace() auto vm = coroutine->GetPandaVM(); auto linker = vm->GetClassLinker(); auto stack_trace_element_class = linker->GetClass("Lstd/core/StackTraceElement;"); - auto context = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS); auto thread = ManagedThread::GetCurrent(); auto walker = StackWalker::Create(thread); -- Gitee