diff --git a/.github/workflows/Dockerfile.trixie b/.github/workflows/Dockerfile.trixie new file mode 100644 index 0000000000..175e0ee826 --- /dev/null +++ b/.github/workflows/Dockerfile.trixie @@ -0,0 +1,65 @@ +FROM debian:trixie +ARG DEBIAN_FRONTEND=noninteractive + +# Compilers +ARG GPP_VERSION=12 +ARG GCC_VERSION=12 + +# Languages +ARG PHP_VERSION=7.4 +ARG PYTHON_VERSION=3.13 + +ARG POSTGRESQL_VERSION=14 +ARG MYSQL_VERSION=8.4-lts + +# Обновляем +RUN apt-get update + +# Ставим свякое для работы +RUN apt-get install -y --no-install-recommends \ + ca-certificates pkg-config lsb-release build-essential wget gnupg \ + git cmake-data cmake make \ + gperf netcat-openbsd patch re2c \ + libfmt-dev libgtest-dev libgmock-dev zlib1g-dev + +# GCC and G++ +RUN apt-get install -y --no-install-recommends gcc-${GCC_VERSION} g++-${GPP_VERSION} \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${GPP_VERSION} 100 \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC_VERSION} 100 + +# Python +RUN apt-get install -y --no-install-recommends \ + python${PYTHON_VERSION}-minimal \ + python${PYTHON_VERSION}-dev \ + python3-pip + +# PostgreSQL +RUN wget -O /etc/apt/trusted.gpg.d/pgdg-key.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + && echo "deb https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update && apt-get install -y --no-install-recommends postgresql-${POSTGRESQL_VERSION} libpq-dev + +# MySQL +RUN wget -O /etc/apt/trusted.gpg.d/mysql.asc https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 \ + && echo "deb https://repo.mysql.com/apt/debian/ trixie mysql-${MYSQL_VERSION}" > /etc/apt/sources.list.d/mysql.list \ + && apt-get update && apt-get install -y --no-install-recommends mysql-server libmysqlclient-dev + +# Python package +COPY tests/python/requirements-trixie.txt /tmp/requirements.txt +RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --break-system-packages -r /tmp/requirements.txt + +# PHP +RUN wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg \ + && echo "deb https://packages.sury.org/php trixie main" > /etc/apt/sources.list.d/php.list \ + && apt-get update && apt-get install -y --no-install-recommends php${PHP_VERSION}-dev \ + && update-alternatives --set php /usr/bin/php${PHP_VERSION} + +# Composer +RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php -r "if (hash_file('sha384', 'composer-setup.php') === 'ed0feb545ba87161262f2d45a633e34f591ebb3381f2e0063c345ebea4d228dd0043083717770234ec00c5a9f9593792') { echo 'Installer verified'.PHP_EOL; } else { echo 'Installer corrupt'.PHP_EOL; unlink('composer-setup.php'); exit(1); }" \ + && php composer-setup.php --install-dir=/usr/bin --version=2.8.12 --filename=composer \ + && php -r "unlink('composer-setup.php');" + +RUN rm -rf /var/lib/apt/lists/* \ + && rm -rf /tmp/* + +RUN useradd -ms /bin/bash kitten diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index a3f06736b8..7d1ee0c737 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -13,6 +13,7 @@ env: jobs: build-linux: + if: ${{ false }} runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e3840f802e..077f307056 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,13 +12,11 @@ env: jobs: build-macos: + if: ${{ false }} runs-on: ${{matrix.os}}-15 strategy: matrix: include: - - os: macos - compiler: clang++ - cpp: 17 - os: macos compiler: clang++ cpp: 20 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index cb05278a43..235afb4e31 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -13,6 +13,7 @@ env: jobs: build-linux: + if: ${{ false }} runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/unstable.yml b/.github/workflows/unstable.yml new file mode 100644 index 0000000000..db644a4d85 --- /dev/null +++ b/.github/workflows/unstable.yml @@ -0,0 +1,115 @@ +name: unstable + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + kphp_root_dir: /home/kitten/kphp + kphp_polyfills_dir: /home/kitten/kphp/kphp-polyfills + kphp_build_dir: /home/kitten/kphp/build + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - os: trixie + compiler: g++-12 + cpp: 20 + asan: off + ubsan: off + mysql: on + pgsql: on + + name: "${{matrix.os}} / ${{matrix.compiler}} / c++${{matrix.cpp}} / asan=${{matrix.asan}} / ubsan=${{matrix.ubsan}}" + + steps: + - uses: actions/checkout@v4 + + - name: Get polyfills repo + uses: actions/checkout@v4 + with: + repository: 'VKCOM/kphp-polyfills' + path: 'kphp-polyfills' + + - name: Cache docker image + uses: actions/cache@v4 + id: docker-image-cache + with: + path: kphp-build-env-${{matrix.os}}.tar + key: docker-image-cache-${{matrix.os}}-${{ hashFiles('.github/workflows/Dockerfile.*', 'tests/python/requirements*.txt') }} + + - name: Build and save docker image + if: steps.docker-image-cache.outputs.cache-hit != 'true' + run: | + docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE \ + -t kphp-build-img-${{matrix.os}} \ + --cache-from=type=local,src=kphp-build-img-${{matrix.os}}-cache + docker tag kphp-build-img-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker save kphp-build-img-${{matrix.os}}-cache -o kphp-build-env-${{matrix.os}}.tar + + - name: Load docker image from cache + if: steps.docker-image-cache.outputs.cache-hit == 'true' + run: docker load --input kphp-build-env-${{matrix.os}}.tar + + - name: Start docker container + run: | + docker run -dt --name kphp-build-container-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker cp $GITHUB_WORKSPACE/. kphp-build-container-${{matrix.os}}:${{env.kphp_root_dir}} + + - name: Add git safe directory + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "git config --global --add safe.directory '${{env.kphp_root_dir}}'" + # This command is used to address potential issues with Git's safe directory feature. + # By setting '*' as a safe directory, we allow Git operations to proceed without errors + # related to directory safety, ensuring smooth execution of the submodules updating. + + - name: Build all + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "cmake -DCMAKE_CXX_COMPILER=${{matrix.compiler}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} -DADDRESS_SANITIZER=${{matrix.asan}} -DUNDEFINED_SANITIZER=${{matrix.ubsan}} -DPDO_DRIVER_MYSQL=${{matrix.mysql}} -DPDO_DRIVER_PGSQL=${{matrix.pgsql}} -DPDO_LIBS_STATIC_LINKING=OFF -S ${{env.kphp_root_dir}} -B ${{env.kphp_build_dir}} && make -C ${{env.kphp_build_dir}} -j$(nproc) all" + + - name: Run unit tests + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "make -C ${{env.kphp_build_dir}} -j$(nproc) test" + + - name: Compile dummy PHP script + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "cd ${{env.kphp_build_dir}} && echo 'hello world' > demo.php && ${{env.kphp_root_dir}}/objs/bin/kphp2cpp --cxx ${{matrix.compiler}} demo.php && kphp_out/server -o --user kitten" + + - name: Polyfills composer install + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "composer install -d ${{env.kphp_polyfills_dir}}" + + - name: Run python tests + id: python_tests + continue-on-error: true + run: docker exec kphp-build-container-${{matrix.os}} bash -c + "chown -R kitten /home && mkdir -p /tmp/pytest-of-kitten && chown -R kitten /tmp/pytest-of-kitten && su kitten -c 'GITHUB_ACTIONS=1 SKIP_MYSQL_TESTS=1 KPHP_TESTS_POLYFILLS_REPO=${{env.kphp_polyfills_dir}} KPHP_CXX=${{matrix.compiler}} python3.13 -m pytest --tb=native -n$(nproc) ${{env.kphp_root_dir}}/tests/python/tests'" + + - name: Prepare python tests artifacts + if: steps.python_tests.outcome == 'failure' + run: docker cp kphp-build-container-${{matrix.os}}:${{env.kphp_root_dir}}/tests/python/_tmp/ ${{runner.temp}} && + rm -rf ${{runner.temp}}/_tmp/*/working_dir + + - name: Upload python tests artifacts + uses: actions/upload-artifact@v4 + if: steps.python_tests.outcome == 'failure' + with: + name: pytest-result-${{ matrix.os }}-${{matrix.compiler}} + path: ${{runner.temp}}/_tmp/ + + - name: Fail pipeline if python tests failed + if: steps.python_tests.outcome == 'failure' + run: exit 1 + + - name: Remove docker container + run: docker rm -f kphp-build-container-${{matrix.os}} diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 56c09db09a..5f952f756c 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -69,7 +69,13 @@ endfunction() function(allow_deprecated_declarations) foreach(src_file ${ARGN}) - set_source_files_properties(${src_file} PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) + set_property(SOURCE ${src_file} APPEND PROPERTY COMPILE_OPTIONS -Wno-deprecated-declarations) + endforeach() +endfunction() + +function(allow_stringop_overflow) + foreach(src_file ${ARGN}) + set_property(SOURCE ${src_file} APPEND PROPERTY COMPILE_OPTIONS -Wno-stringop-overflow) endforeach() endfunction() diff --git a/common/binlog/binlog-buffer-replay.cpp b/common/binlog/binlog-buffer-replay.cpp index daa6e0a18e..56f2171bbe 100644 --- a/common/binlog/binlog-buffer-replay.cpp +++ b/common/binlog/binlog-buffer-replay.cpp @@ -53,9 +53,9 @@ static int bb_writer_rotate(bb_writer_t* W, bb_rotation_point_t* p) { /******************** replay binlog ********************/ void bbr_replay_init(bb_reader_t* R) { - bbr_replay_extra_t* e = static_cast(calloc(sizeof(*e), 1)); + bbr_replay_extra_t* e = static_cast(calloc(1, sizeof(*e))); assert(e); - e->wait_job_cb = static_cast(calloc(sizeof(*e->wait_job_cb), 1)); + e->wait_job_cb = static_cast(calloc(1, sizeof(*e->wait_job_cb))); R->extra = e; } diff --git a/common/binlog/binlog-buffer-rotation-points.cpp b/common/binlog/binlog-buffer-rotation-points.cpp index 0de46c8b12..968ce215d4 100644 --- a/common/binlog/binlog-buffer-rotation-points.cpp +++ b/common/binlog/binlog-buffer-rotation-points.cpp @@ -79,7 +79,7 @@ static void bb_buffer_insert_rotation_point(bb_buffer_t* B, bb_rotation_point_t* } bb_rotation_point_t* bb_rotation_point_alloc(bb_buffer_t* B, enum bb_rotation_point_type tp, long long log_pos) { - auto* p = static_cast(calloc(sizeof(bb_rotation_point_t), 1)); + auto* p = static_cast(calloc(1, sizeof(bb_rotation_point_t))); assert(p); p->tp = tp; p->log_pos = log_pos; diff --git a/common/kfs/kfs-replica.cpp b/common/kfs/kfs-replica.cpp index f744aad257..909bd5d485 100644 --- a/common/kfs/kfs-replica.cpp +++ b/common/kfs/kfs-replica.cpp @@ -113,7 +113,7 @@ kfs_replica_handle_t open_replica(const char* replica_name, int flags) { struct kfs_replica* R = 0; if (flags & KFS_OPEN_REPLICA_FLAG_FORCE) { - R = static_cast(calloc(sizeof(*R), 1)); + R = static_cast(calloc(1, sizeof(*R))); assert(R); R->replica_prefix = strdup(replica_name); assert(R->replica_prefix); diff --git a/common/php-functions.h b/common/php-functions.h index 4c8e5c5114..16de3af546 100644 --- a/common/php-functions.h +++ b/common/php-functions.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/common/sanitizer.h b/common/sanitizer.h index cbc415f61b..bd6ba8ebb4 100644 --- a/common/sanitizer.h +++ b/common/sanitizer.h @@ -4,9 +4,8 @@ #pragma once -#include - #if defined(__SANITIZE_ADDRESS__) +#include #define ASAN_ENABLED 1 #elif defined(__has_feature) #if __has_feature(address_sanitizer) diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index 3fcdcab68e..4d697b978a 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -225,9 +225,36 @@ prepend(KPHP_COMPILER_SOURCES ${KPHP_COMPILER_DIR}/ utils/string-utils.cpp) # Suppress YAML-cpp-related warnings -if(COMPILER_CLANG) - allow_deprecated_declarations(${KPHP_COMPILER_DIR}/data/composer-json-data.cpp) - allow_deprecated_declarations(${KPHP_COMPILER_DIR}/data/modulite-data.cpp) +if(COMPILER_CLANG OR (COMPILER_GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0.0"))) + allow_deprecated_declarations( + ${KPHP_COMPILER_DIR}/data/composer-json-data.cpp + ${KPHP_COMPILER_DIR}/data/modulite-data.cpp + ) +endif() + +if(COMPILER_GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0.0")) + allow_stringop_overflow( + ${KPHP_COMPILER_DIR}/code-gen/vertex-compiler.cpp + ${KPHP_COMPILER_DIR}/data/class-data.cpp + ${KPHP_COMPILER_DIR}/data/kphp-json-tags.cpp + ${KPHP_COMPILER_DIR}/data/generics-mixins.cpp + ${KPHP_COMPILER_DIR}/data/kphp-tracing-tags.cpp + ${KPHP_COMPILER_DIR}/data/modulite-data.cpp + ${KPHP_COMPILER_DIR}/code-gen/files/tracing-autogen.cpp + ${KPHP_COMPILER_DIR}/pipes/analyze-performance.cpp + ${KPHP_COMPILER_DIR}/pipes/check-access-modifiers.cpp + ${KPHP_COMPILER_DIR}/pipes/check-classes.cpp + ${KPHP_COMPILER_DIR}/pipes/check-tl-classes.cpp + ${KPHP_COMPILER_DIR}/pipes/code-gen.cpp + ${KPHP_COMPILER_DIR}/pipes/filter-only-actually-used.cpp + ${KPHP_COMPILER_DIR}/pipes/final-check.cpp + ${KPHP_COMPILER_DIR}/pipes/parse-and-apply-phpdoc.cpp + ${KPHP_COMPILER_DIR}/pipes/sort-and-inherit-classes.cpp + ${KPHP_COMPILER_DIR}/pipes/register-kphp-configuration.cpp + ${KPHP_COMPILER_DIR}/phpdoc.cpp + ${KPHP_COMPILER_DIR}/gentree.cpp + ${KPHP_COMPILER_DIR}/make/make.cpp + ) endif() if(APPLE) diff --git a/compiler/ffi/c_parser/parsing_driver.cpp b/compiler/ffi/c_parser/parsing_driver.cpp index a20c1f82a7..561ad01904 100644 --- a/compiler/ffi/c_parser/parsing_driver.cpp +++ b/compiler/ffi/c_parser/parsing_driver.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace ffi; @@ -113,7 +114,7 @@ FFIType *ParsingDriver::function_to_var(FFIType *function) { function->kind = FFITypeKind::Var; FFIType *function_ptr_type = alloc.new_type(FFITypeKind::FunctionPointer); function_ptr_type->members = std::move(function->members); - function->members = {function_ptr_type}; + function->members = std::vector{function_ptr_type}; return function; } diff --git a/net/net-aes-keys.cpp b/net/net-aes-keys.cpp index 97d1520701..253e3896f3 100644 --- a/net/net-aes-keys.cpp +++ b/net/net-aes-keys.cpp @@ -31,7 +31,7 @@ static size_t aes_loaded_keys_size; aes_key_t *default_aes_key; aes_key_t *create_aes_key() { - aes_key_t *key = static_cast(calloc(sizeof(*key), 1)); + aes_key_t *key = static_cast(calloc(1, sizeof(*key))); assert(!posix_memalign((void **) &key->key, 4096, AES_KEY_MAX_LEN)); our_madvise(key->key, AES_KEY_MAX_LEN, MADV_DONTDUMP); diff --git a/runtime-common/core/core-types/definition/array.inl b/runtime-common/core/core-types/definition/array.inl index ce1270adae..0f0990ef57 100644 --- a/runtime-common/core/core-types/definition/array.inl +++ b/runtime-common/core/core-types/definition/array.inl @@ -107,14 +107,24 @@ template<> inline typename array::array_inner* array::array_inner::empty_array() { // need this hack because gcc10 and newer complains about // "array subscript is outside array bounds of array::array_inner" - static array_inner_control empty_array[1]{{ - true, - ExtraRefCnt::for_global_const, - -1, - {0, 0}, - 0, - 2, - }}; + static array_inner_control empty_array[2]{ + { + true, + ExtraRefCnt::for_global_const, + -1, + {0, 0}, + 0, + 2, + }, + { + true, + ExtraRefCnt::for_global_const, + -1, + {0, 0}, + 0, + 2, + }, + }; return static_cast::array_inner*>(&empty_array[0]); } diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 9c161d5864..869b9cc560 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -132,7 +132,7 @@ set_source_files_properties( ) # Suppress YAML-cpp-related warnings -if(COMPILER_CLANG) +if(COMPILER_CLANG OR (COMPILER_GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0.0"))) allow_deprecated_declarations(${BASE_DIR}/runtime/interface.cpp) endif() diff --git a/server/server.cmake b/server/server.cmake index 59ac5f311c..fd1b5dbf9f 100644 --- a/server/server.cmake +++ b/server/server.cmake @@ -31,12 +31,14 @@ prepend(KPHP_SERVER_SOURCES ${BASE_DIR}/server/ signal-handlers.cpp) # Suppress YAML-cpp-related warnings -if(COMPILER_CLANG) - allow_deprecated_declarations(${BASE_DIR}/server/json-logger.cpp) - allow_deprecated_declarations(${BASE_DIR}/server/lease-config-parser.cpp) - allow_deprecated_declarations(${BASE_DIR}/server/php-engine.cpp) - allow_deprecated_declarations(${BASE_DIR}/server/php-master.cpp) - allow_deprecated_declarations(${BASE_DIR}/server/server-config.cpp) +if(COMPILER_CLANG OR (COMPILER_GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0.0"))) + allow_deprecated_declarations( + ${BASE_DIR}/server/json-logger.cpp + ${BASE_DIR}/server/lease-config-parser.cpp + ${BASE_DIR}/server/php-engine.cpp + ${BASE_DIR}/server/php-master.cpp + ${BASE_DIR}/server/server-config.cpp + ) endif() prepend(KPHP_JOB_WORKERS_SOURCES ${BASE_DIR}/server/job-workers/ diff --git a/tests/cpp/server/server-tests.cmake b/tests/cpp/server/server-tests.cmake index 98827729e2..ebfd72f529 100644 --- a/tests/cpp/server/server-tests.cmake +++ b/tests/cpp/server/server-tests.cmake @@ -7,7 +7,7 @@ prepend(SERVER_TESTS_SOURCES ${BASE_DIR}/tests/cpp/server/ workers-control-test.cpp) # Suppress YAML-cpp-related warnings -if(COMPILER_CLANG) +if(COMPILER_CLANG OR (COMPILER_GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0.0"))) allow_deprecated_declarations(${BASE_DIR}/tests/cpp/server/server-config-test.cpp) endif() diff --git a/tests/python/requirements-trixie.txt b/tests/python/requirements-trixie.txt new file mode 100644 index 0000000000..fe3c1b7c63 --- /dev/null +++ b/tests/python/requirements-trixie.txt @@ -0,0 +1,14 @@ +jsonschema==4.17.3 +portalocker==2.7.0 +psutil==5.9.5 +requests==2.31.0 +urllib3==1.26.12 +requests-toolbelt==0.9.1 +pytest==8.0.0 +pytest-mysql==3.1.0 +pytest-postgresql==6.0.0 +pytest-durations==1.6.1 +psycopg==3.2.2 +pytest-xdist==3.3.1 +zstandard==0.21.0 +six==1.17.0 diff --git a/tests/python/tests/sql_client/test_mysql.py b/tests/python/tests/sql_client/test_mysql.py index c7a58ee3fd..41e4b6ce87 100644 --- a/tests/python/tests/sql_client/test_mysql.py +++ b/tests/python/tests/sql_client/test_mysql.py @@ -1,3 +1,4 @@ +import os import re import pytest @@ -12,6 +13,8 @@ """ @pytest.fixture(scope="session") def mysql_proc_wrapper(request): + if 'SKIP_MYSQL_TESTS' in os.environ: + pytest.skip("Skipping mysql_proc in unstable environment") if search_k2_bin() is not None: pytest.skip("Skipping mysql_proc in K2 mode") yield request.getfixturevalue("mysql_proc") @@ -40,7 +43,11 @@ def _setup_mysql_db(self, mysql, mysql_proc_wrapper): val_float FLOAT, PRIMARY KEY (id) ); + ''' + ) + cursor.execute( + ''' INSERT INTO TestTable (id, val_str, val_float) VALUES diff --git a/vkext/vkext-rpc.cpp b/vkext/vkext-rpc.cpp index 6f5512327c..1096c52d15 100644 --- a/vkext/vkext-rpc.cpp +++ b/vkext/vkext-rpc.cpp @@ -155,7 +155,9 @@ void update_precise_now() { } rpc_connection *rpc_connection_get(int fd) { - rpc_connection **T = tree_lookup_value_connection(rpc_connection_tree, reinterpret_cast(&fd)); + static rpc_connection dummy_rpc_connection{}; + dummy_rpc_connection.fd = fd; + rpc_connection **T = tree_lookup_value_connection(rpc_connection_tree, reinterpret_cast(&dummy_rpc_connection)); return T ? *T : 0; }