From db90326c4becb12fb5d3c450523c136a5a139d30 Mon Sep 17 00:00:00 2001 From: Jon Haslam Date: Mon, 19 Dec 2022 06:37:51 -0800 Subject: [PATCH] Initial commit --- .circleci/config.yml | 123 + .clang-format | 8 + .clang-tidy | 31 + .editorconfig | 14 + .git-blame-ignore-revs | 2 + .github/pull_request_template.md | 7 + .gitignore | 30 + .gitmodules | 9 + CHANGELOG.md | 1 + CMakeLists.txt | 376 + CODE_OF_CONDUCT.md | 76 + CONTRIBUTING.md | 31 + LICENSE | 201 + README.org | 14 + cmake/CompilerWarnings.cmake | 84 + cmake/FindThrift.cmake | 24 + cmake/Findzstd.cmake | 29 + cmake/PreventInSourceBuilds.cmake | 18 + cmake/StandardProjectSettings.cmake | 42 + dev.oid.toml | 48 + examples/compile-time-oil/.gitignore | 6 + examples/compile-time-oil/Makefile | 28 + .../compile-time-oil/OilVectorOfStrings.cpp | 38 + examples/oil-metrics/Metrics.cpp | 119 + examples/oil-metrics/Metrics.h | 123 + examples/web/AddrBook/AddrBook.cpp | 103 + examples/web/AddrBook/Makefile | 16 + extern/drgn | 1 + extern/folly | 1 + extern/rocksdb | 1 + include/ObjectIntrospection.h | 255 + oss.oid.toml | 9 + src/Common.h | 59 + src/ContainerInfo.cpp | 133 + src/ContainerInfo.h | 91 + src/Descs.cpp | 128 + src/Descs.h | 142 + src/DrgnUtils.cpp | 72 + src/DrgnUtils.h | 114 + src/FuncGen.cpp | 509 + src/FuncGen.h | 78 + src/Metrics.cpp | 207 + src/Metrics.h | 173 + src/OICache.cpp | 247 + src/OICache.h | 70 + src/OICodeGen.cpp | 3728 ++++ src/OICodeGen.h | 332 + src/OICompile.cpp | 203 + src/OICompiler.cpp | 638 + src/OICompiler.h | 222 + src/OID.cpp | 720 + src/OIDebugger.cpp | 2984 +++ src/OIDebugger.h | 278 + src/OIGenerator.cpp | 168 + src/OIGenerator.h | 56 + src/OILexer.h | 51 + src/OILexer.l | 99 + src/OILibrary.cpp | 63 + src/OILibraryImpl.cpp | 298 + src/OILibraryImpl.h | 56 + src/OIOpts.h | 114 + src/OIParser.h | 120 + src/OIParser.yy | 106 + src/OITraceCode.cpp | 157 + src/OIUtils.cpp | 157 + src/OIUtils.h | 25 + src/PaddingHunter.cpp | 72 + src/PaddingHunter.h | 88 + src/Serialize.cpp | 386 + src/Serialize.h | 80 + src/SymbolService.cpp | 731 + src/SymbolService.h | 65 + src/Syscall.h | 75 + src/TimeUtils.h | 24 + src/TrapInfo.h | 134 + src/TreeBuilder.cpp | 894 + src/TreeBuilder.h | 114 + src/X86InstDefs.h | 26 + test/CMakeLists.txt | 65 + test/Makefile | 14 + test/TARGETS | 31 + test/ci.oid.toml | 58 + test/convert_to_junit.sh | 18 + test/ctest_to_junit.xsl | 129 + test/inlined_test.cpp | 70 + test/inlined_test_flatten.oid | 1 + test/inlined_test_flatten_combine.oid | 1 + test/integration.py | 300 + test/integration/CMakeLists.txt | 130 + test/integration/README.md | 273 + test/integration/anonymous.toml | 312 + test/integration/container_enums.toml | 32 + test/integration/cycles.toml | 134 + test/integration/gen_tests.py | 452 + test/integration/ignored.toml | 38 + test/integration/multi_arg.toml | 49 + test/integration/namespaces.toml | 31 + test/integration/packed.toml | 19 + test/integration/padding.toml | 102 + test/integration/pointers.toml | 238 + test/integration/pointers_function.toml | 96 + test/integration/pointers_incomplete.toml | 114 + test/integration/primitives.toml | 73 + test/integration/references.toml | 50 + test/integration/runner_common.cpp | 358 + test/integration/runner_common.h | 72 + ...imple_multiple_multilevel_inheritance.toml | 39 + test/integration/simple_struct.toml | 20 + test/integration/std_array.toml | 88 + test/integration/std_deque.toml | 30 + test/integration/std_deque_del_allocator.toml | 56 + test/integration/std_list_del_allocator.toml | 59 + .../std_map_custom_comparator.toml | 69 + test/integration/std_optional.toml | 65 + test/integration/std_pair.toml | 56 + test/integration/std_priority_queue.toml | 50 + test/integration/std_queue.toml | 108 + test/integration/std_reference_wrapper.toml | 20 + .../std_set_custom_comparator.toml | 64 + test/integration/std_smart_ptr.toml | 178 + test/integration/std_stack.toml | 108 + test/integration/std_string.toml | 68 + test/integration/std_unordered_map.toml | 8 + .../std_unordered_map_custom_operator.toml | 66 + .../std_unordered_set_custom_operator.toml | 66 + test/integration/std_variant.toml | 153 + test/integration/std_vector.toml | 46 + .../integration/std_vector_del_allocator.toml | 59 + test/integration/thrift_isset.toml | 227 + test/integration/thrift_isset_missing.toml | 142 + test/integration/thrift_namespaces.toml | 26 + test/integration/typedefed_parent.toml | 42 + test/integration_cycles.cpp | 83 + test/integration_cycles_raw.oid | 1 + test/integration_cycles_shared.oid | 1 + test/integration_cycles_unique.oid | 1 + test/integration_entry_badFunc_arg0.oid | 1 + test/integration_entry_doStuff_arg0.oid | 1 + test/integration_entry_doStuff_arg0_arg1.oid | 1 + test/integration_entry_doStuff_this.oid | 1 + test/integration_entry_incVectSize_arg0.oid | 1 + test/integration_entry_inc_arg0.oid | 1 + test/integration_entry_inc_this.oid | 1 + test/integration_global_myGlobalFoo.oid | 1 + test/integration_mt.cpp | 60 + test/integration_mt_entry_doStuff_arg0.oid | 1 + test/integration_mttest.cpp | 344 + test/integration_packed.cpp | 89 + test/integration_packed_arg0.oid | 1 + test/integration_return_doStuff_arg0.oid | 1 + test/integration_return_doStuff_retval.oid | 1 + test/integration_return_incN_arg0.oid | 1 + test/integration_return_inc_this.oid | 1 + test/integration_sleepy.cpp | 266 + test/mapiter.cpp | 58 + test/mttest.h | 25 + test/mttest1.cpp | 146 + test/mttest2.cpp | 254 + ...mttest2_do_it_arg0_script_void_pointer.oid | 1 + test/mttest2_do_stuff_arg0_script.oid | 1 + .../mttest2_do_stuff_arg0_script_comments.oid | 14 + test/mttest2_do_stuff_arg0_script_spaces.oid | 1 + test/mttest2_inline.cpp | 1 + test/mttest2_inline_script.oid | 1 + test/mttest3.cpp | 233 + test/mttest4.cpp | 248 + test/test_compiler.cpp | 283 + test/test_parser.cpp | 216 + test/userDef1.cpp | 44 + test/vector.cpp | 115 + tools/OILGen.cpp | 87 + tools/OIP.cpp | 340 + tools/OITB.cpp | 203 + tools/combine_configs.py | 65 + tools/config_gen.py | 241 + tools/inline_stats.py | 367 + types/array_type.toml | 30 + types/boost_bimap_type.toml | 33 + types/caffe2_blob_type.toml | 92 + types/cxx11_list_type.toml | 31 + types/cxx11_string_type.toml | 35 + types/deque_list_type.toml | 31 + types/f14_fast_map.toml | 34 + types/f14_fast_set.toml | 33 + types/f14_node_map.toml | 34 + types/f14_node_set.toml | 33 + types/f14_vector_map.toml | 34 + types/f14_vector_set.toml | 33 + types/fb_string_type.toml | 25 + types/folly_iobuf_queue_type.toml | 26 + types/folly_iobuf_type.toml | 39 + types/folly_optional_type.toml | 30 + types/folly_small_heap_vector_map.toml | 35 + types/list_type.toml | 31 + types/map_seq_type.toml | 35 + types/multi_map_type.toml | 31 + types/optional_type.toml | 29 + types/pair_type.toml | 26 + ...priority_queue_container_adapter_type.toml | 31 + types/queue_container_adapter_type.toml | 31 + types/ref_wrapper_type.toml | 25 + types/repeated_field_type.toml | 29 + types/repeated_ptr_field_type.toml | 29 + types/seq_type.toml | 34 + types/set_type.toml | 34 + types/shrd_ptr_type.toml | 31 + types/small_vec_type.toml | 30 + types/sorted_vec_set_type.toml | 34 + types/stack_container_adapter_type.toml | 31 + types/std_map_type.toml | 35 + types/std_unordered_map_type.toml | 37 + types/std_variant.toml | 42 + types/string_type.toml | 35 + types/thrift_isset_type.toml | 17 + types/try_type.toml | 29 + types/uniq_ptr_type.toml | 31 + types/unordered_set_type.toml | 35 + website/README.md | 49 + website/babel.config.js | 12 + website/docs/addrbook-funcargs.md | 83 + website/docs/addrbook-intro.md | 61 + website/docs/addrbook-this.md | 189 + website/docs/constraints.md | 20 + website/docs/contributing.md | 6 + website/docs/getting-started.md | 70 + website/docs/intro.md | 23 + website/docusaurus.config.js | 127 + website/package-lock.json | 15269 ++++++++++++++++ website/package.json | 56 + website/sidebars.js | 33 + website/src/css/custom.css | 37 + website/src/pages/index.js | 106 + website/src/pages/markdown-page.md | 7 + website/src/pages/styles.module.css | 44 + website/static/.DS_Store | Bin 0 -> 6148 bytes website/static/._.DS_Store | Bin 0 -> 120 bytes website/static/.nojekyll | 0 website/static/img/._OIBrandmark.svg | Bin 0 -> 724 bytes website/static/img/._OISymbol.svg | Bin 0 -> 624 bytes .../._ObjectIntrospection-SecondarySymbol.svg | Bin 0 -> 624 bytes website/static/img/OIBrandmark.svg | 1 + website/static/img/OISymbol.svg | 1 + .../ObjectIntrospection-SecondarySymbol.svg | 1 + website/static/img/docusaurus.png | Bin 0 -> 5142 bytes website/static/img/favicon.ico | Bin 0 -> 3626 bytes website/static/img/logo.svg | 1 + website/static/img/meta_opensource_logo.svg | 1 + .../img/meta_opensource_logo_negative.svg | 1 + .../static/img/undraw_docusaurus_mountain.svg | 171 + .../static/img/undraw_docusaurus_react.svg | 170 + website/static/img/undraw_docusaurus_tree.svg | 40 + 251 files changed, 44070 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig create mode 100644 .git-blame-ignore-revs create mode 100644 .github/pull_request_template.md create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CHANGELOG.md create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.org create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/FindThrift.cmake create mode 100644 cmake/Findzstd.cmake create mode 100644 cmake/PreventInSourceBuilds.cmake create mode 100644 cmake/StandardProjectSettings.cmake create mode 100644 dev.oid.toml create mode 100644 examples/compile-time-oil/.gitignore create mode 100644 examples/compile-time-oil/Makefile create mode 100644 examples/compile-time-oil/OilVectorOfStrings.cpp create mode 100644 examples/oil-metrics/Metrics.cpp create mode 100644 examples/oil-metrics/Metrics.h create mode 100644 examples/web/AddrBook/AddrBook.cpp create mode 100644 examples/web/AddrBook/Makefile create mode 160000 extern/drgn create mode 160000 extern/folly create mode 160000 extern/rocksdb create mode 100644 include/ObjectIntrospection.h create mode 100644 oss.oid.toml create mode 100644 src/Common.h create mode 100644 src/ContainerInfo.cpp create mode 100644 src/ContainerInfo.h create mode 100644 src/Descs.cpp create mode 100644 src/Descs.h create mode 100644 src/DrgnUtils.cpp create mode 100644 src/DrgnUtils.h create mode 100644 src/FuncGen.cpp create mode 100644 src/FuncGen.h create mode 100644 src/Metrics.cpp create mode 100644 src/Metrics.h create mode 100644 src/OICache.cpp create mode 100644 src/OICache.h create mode 100644 src/OICodeGen.cpp create mode 100644 src/OICodeGen.h create mode 100644 src/OICompile.cpp create mode 100644 src/OICompiler.cpp create mode 100644 src/OICompiler.h create mode 100644 src/OID.cpp create mode 100644 src/OIDebugger.cpp create mode 100644 src/OIDebugger.h create mode 100644 src/OIGenerator.cpp create mode 100644 src/OIGenerator.h create mode 100644 src/OILexer.h create mode 100644 src/OILexer.l create mode 100644 src/OILibrary.cpp create mode 100644 src/OILibraryImpl.cpp create mode 100644 src/OILibraryImpl.h create mode 100644 src/OIOpts.h create mode 100644 src/OIParser.h create mode 100644 src/OIParser.yy create mode 100644 src/OITraceCode.cpp create mode 100644 src/OIUtils.cpp create mode 100644 src/OIUtils.h create mode 100644 src/PaddingHunter.cpp create mode 100644 src/PaddingHunter.h create mode 100644 src/Serialize.cpp create mode 100644 src/Serialize.h create mode 100644 src/SymbolService.cpp create mode 100644 src/SymbolService.h create mode 100644 src/Syscall.h create mode 100644 src/TimeUtils.h create mode 100644 src/TrapInfo.h create mode 100644 src/TreeBuilder.cpp create mode 100644 src/TreeBuilder.h create mode 100644 src/X86InstDefs.h create mode 100644 test/CMakeLists.txt create mode 100644 test/Makefile create mode 100644 test/TARGETS create mode 100644 test/ci.oid.toml create mode 100755 test/convert_to_junit.sh create mode 100644 test/ctest_to_junit.xsl create mode 100644 test/inlined_test.cpp create mode 100644 test/inlined_test_flatten.oid create mode 100644 test/inlined_test_flatten_combine.oid create mode 100644 test/integration.py create mode 100644 test/integration/CMakeLists.txt create mode 100644 test/integration/README.md create mode 100644 test/integration/anonymous.toml create mode 100644 test/integration/container_enums.toml create mode 100644 test/integration/cycles.toml create mode 100644 test/integration/gen_tests.py create mode 100644 test/integration/ignored.toml create mode 100644 test/integration/multi_arg.toml create mode 100644 test/integration/namespaces.toml create mode 100644 test/integration/packed.toml create mode 100644 test/integration/padding.toml create mode 100644 test/integration/pointers.toml create mode 100644 test/integration/pointers_function.toml create mode 100644 test/integration/pointers_incomplete.toml create mode 100644 test/integration/primitives.toml create mode 100644 test/integration/references.toml create mode 100644 test/integration/runner_common.cpp create mode 100644 test/integration/runner_common.h create mode 100644 test/integration/simple_multiple_multilevel_inheritance.toml create mode 100644 test/integration/simple_struct.toml create mode 100644 test/integration/std_array.toml create mode 100644 test/integration/std_deque.toml create mode 100644 test/integration/std_deque_del_allocator.toml create mode 100644 test/integration/std_list_del_allocator.toml create mode 100644 test/integration/std_map_custom_comparator.toml create mode 100644 test/integration/std_optional.toml create mode 100644 test/integration/std_pair.toml create mode 100644 test/integration/std_priority_queue.toml create mode 100644 test/integration/std_queue.toml create mode 100644 test/integration/std_reference_wrapper.toml create mode 100644 test/integration/std_set_custom_comparator.toml create mode 100644 test/integration/std_smart_ptr.toml create mode 100644 test/integration/std_stack.toml create mode 100644 test/integration/std_string.toml create mode 100644 test/integration/std_unordered_map.toml create mode 100644 test/integration/std_unordered_map_custom_operator.toml create mode 100644 test/integration/std_unordered_set_custom_operator.toml create mode 100644 test/integration/std_variant.toml create mode 100644 test/integration/std_vector.toml create mode 100644 test/integration/std_vector_del_allocator.toml create mode 100644 test/integration/thrift_isset.toml create mode 100644 test/integration/thrift_isset_missing.toml create mode 100644 test/integration/thrift_namespaces.toml create mode 100644 test/integration/typedefed_parent.toml create mode 100644 test/integration_cycles.cpp create mode 100644 test/integration_cycles_raw.oid create mode 100644 test/integration_cycles_shared.oid create mode 100644 test/integration_cycles_unique.oid create mode 100644 test/integration_entry_badFunc_arg0.oid create mode 100644 test/integration_entry_doStuff_arg0.oid create mode 100644 test/integration_entry_doStuff_arg0_arg1.oid create mode 100644 test/integration_entry_doStuff_this.oid create mode 100644 test/integration_entry_incVectSize_arg0.oid create mode 100644 test/integration_entry_inc_arg0.oid create mode 100644 test/integration_entry_inc_this.oid create mode 100644 test/integration_global_myGlobalFoo.oid create mode 100644 test/integration_mt.cpp create mode 100644 test/integration_mt_entry_doStuff_arg0.oid create mode 100644 test/integration_mttest.cpp create mode 100644 test/integration_packed.cpp create mode 100644 test/integration_packed_arg0.oid create mode 100644 test/integration_return_doStuff_arg0.oid create mode 100644 test/integration_return_doStuff_retval.oid create mode 100644 test/integration_return_incN_arg0.oid create mode 100644 test/integration_return_inc_this.oid create mode 100644 test/integration_sleepy.cpp create mode 100644 test/mapiter.cpp create mode 100644 test/mttest.h create mode 100644 test/mttest1.cpp create mode 100644 test/mttest2.cpp create mode 100644 test/mttest2_do_it_arg0_script_void_pointer.oid create mode 100644 test/mttest2_do_stuff_arg0_script.oid create mode 100644 test/mttest2_do_stuff_arg0_script_comments.oid create mode 100644 test/mttest2_do_stuff_arg0_script_spaces.oid create mode 120000 test/mttest2_inline.cpp create mode 100644 test/mttest2_inline_script.oid create mode 100644 test/mttest3.cpp create mode 100644 test/mttest4.cpp create mode 100644 test/test_compiler.cpp create mode 100644 test/test_parser.cpp create mode 100644 test/userDef1.cpp create mode 100644 test/vector.cpp create mode 100644 tools/OILGen.cpp create mode 100644 tools/OIP.cpp create mode 100644 tools/OITB.cpp create mode 100644 tools/combine_configs.py create mode 100755 tools/config_gen.py create mode 100755 tools/inline_stats.py create mode 100644 types/array_type.toml create mode 100644 types/boost_bimap_type.toml create mode 100644 types/caffe2_blob_type.toml create mode 100644 types/cxx11_list_type.toml create mode 100644 types/cxx11_string_type.toml create mode 100644 types/deque_list_type.toml create mode 100644 types/f14_fast_map.toml create mode 100644 types/f14_fast_set.toml create mode 100644 types/f14_node_map.toml create mode 100644 types/f14_node_set.toml create mode 100644 types/f14_vector_map.toml create mode 100644 types/f14_vector_set.toml create mode 100644 types/fb_string_type.toml create mode 100644 types/folly_iobuf_queue_type.toml create mode 100644 types/folly_iobuf_type.toml create mode 100644 types/folly_optional_type.toml create mode 100644 types/folly_small_heap_vector_map.toml create mode 100644 types/list_type.toml create mode 100644 types/map_seq_type.toml create mode 100644 types/multi_map_type.toml create mode 100644 types/optional_type.toml create mode 100644 types/pair_type.toml create mode 100644 types/priority_queue_container_adapter_type.toml create mode 100644 types/queue_container_adapter_type.toml create mode 100644 types/ref_wrapper_type.toml create mode 100644 types/repeated_field_type.toml create mode 100644 types/repeated_ptr_field_type.toml create mode 100644 types/seq_type.toml create mode 100644 types/set_type.toml create mode 100644 types/shrd_ptr_type.toml create mode 100644 types/small_vec_type.toml create mode 100644 types/sorted_vec_set_type.toml create mode 100644 types/stack_container_adapter_type.toml create mode 100644 types/std_map_type.toml create mode 100644 types/std_unordered_map_type.toml create mode 100644 types/std_variant.toml create mode 100644 types/string_type.toml create mode 100644 types/thrift_isset_type.toml create mode 100644 types/try_type.toml create mode 100644 types/uniq_ptr_type.toml create mode 100644 types/unordered_set_type.toml create mode 100644 website/README.md create mode 100644 website/babel.config.js create mode 100644 website/docs/addrbook-funcargs.md create mode 100644 website/docs/addrbook-intro.md create mode 100644 website/docs/addrbook-this.md create mode 100644 website/docs/constraints.md create mode 100644 website/docs/contributing.md create mode 100644 website/docs/getting-started.md create mode 100644 website/docs/intro.md create mode 100644 website/docusaurus.config.js create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/sidebars.js create mode 100644 website/src/css/custom.css create mode 100644 website/src/pages/index.js create mode 100644 website/src/pages/markdown-page.md create mode 100644 website/src/pages/styles.module.css create mode 100644 website/static/.DS_Store create mode 100644 website/static/._.DS_Store create mode 100644 website/static/.nojekyll create mode 100755 website/static/img/._OIBrandmark.svg create mode 100755 website/static/img/._OISymbol.svg create mode 100755 website/static/img/._ObjectIntrospection-SecondarySymbol.svg create mode 100755 website/static/img/OIBrandmark.svg create mode 100755 website/static/img/OISymbol.svg create mode 100755 website/static/img/ObjectIntrospection-SecondarySymbol.svg create mode 100644 website/static/img/docusaurus.png create mode 100644 website/static/img/favicon.ico create mode 100644 website/static/img/logo.svg create mode 100644 website/static/img/meta_opensource_logo.svg create mode 100644 website/static/img/meta_opensource_logo_negative.svg create mode 100644 website/static/img/undraw_docusaurus_mountain.svg create mode 100644 website/static/img/undraw_docusaurus_react.svg create mode 100644 website/static/img/undraw_docusaurus_tree.svg diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..d9e41b3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,123 @@ +version: 2.1 + +workflows: + build-test: + jobs: + - lint + - build-test: + name: build-test-gcc + cc: /usr/bin/gcc + cxx: /usr/bin/g++ + - build-test: + name: build-test-clang + cc: /usr/bin/clang-12 + cxx: /usr/bin/clang++-12 + +jobs: + lint: + docker: + - image: ubuntu:jammy + steps: + - run: + name: Install dependencies + command: | + apt-get update + apt-get install -y \ + clang-format \ + git \ + python3-pip + # click broke semver with 8.1.0, causing issues for black + pip install click==8.0.0 black isort + - checkout + - run: + name: clang-format + command: | + git ls-files '*.cpp' '*.h' | xargs clang-format --fallback-style=Google -i + git ls-files '*.py' | xargs black + git ls-files '*.py' | xargs isort + git diff --exit-code + - run: + name: python linting + command: | + black --check --diff test/ + isort --check --diff test/ + + build-test: + machine: + image: ubuntu-2204:2022.10.2 + resource_class: 2xlarge + parameters: + cc: + type: string + cxx: + type: string + environment: + CC: << parameters.cc >> + CXX: << parameters.cxx >> + steps: + - checkout + - run: + name: Install dependencies + command: | + sudo rm -f /etc/apt/sources.list.d/heroku.list + sudo apt-get update + sudo apt-get install -y \ + bison \ + build-essential \ + clang-12 \ + cmake \ + flex \ + gawk \ + libboost-all-dev \ + libbz2-dev \ + libcap2-bin \ + libclang-12-dev \ + libcurl4-gnutls-dev \ + libdouble-conversion-dev \ + libdw-dev \ + libfmt-dev \ + libgflags-dev \ + libgmock-dev \ + libgoogle-glog-dev \ + libgtest-dev \ + libjemalloc-dev \ + libmsgpack-dev \ + libzstd-dev \ + llvm-12-dev \ + ninja-build \ + pkg-config \ + python3-setuptools \ + sudo \ + xsltproc + pip3 install toml + environment: + DEBIAN_FRONTEND: noninteractive + + - run: + name: Build + command: | + cmake -G Ninja -B build/ -DWITH_TESTS=On + cmake --build build/ -j + + - run: + name: Test + environment: + # disable drgn multithreading as tests are already run in parallel + OMP_NUM_THREADS: 1 + command: | + echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope + cp test/ci.oid.toml build/testing.oid.toml + ctest --test-dir build/test/ --test-action Test -j$(nproc) \ + --no-compress-output --output-on-failure \ + --exclude-regex 'TestTypes\/ComparativeTest\..*' \ + --schedule-random --timeout 30 --repeat until-pass:2 + + - run: + name: Convert test results + when: always + command: | + mkdir -p build/results/ctest + bash test/convert_to_junit.sh build/test + + - store_test_results: + path: build/results diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8492874 --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +--- +Language: Cpp +BasedOnStyle: Google +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..6f8954e --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,31 @@ +Checks: > + -*, + bugprone*, + readability*, + cppcoreguidelines*, + clang-analyzer*, + performance*, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -readability-function-cognitive-complexity + +CheckOptions: + - { key: readability-identifier-naming.NamespaceCase, value: lower_case } + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } + - { key: readability-identifier-naming.FunctionCase, value: camelBack } + - { key: readability-identifier-naming.VariableCase, value: camelBack } + - { key: readability-identifier-naming.ClassMemberCase, value: camelBack } + - { key: readability-identifier-naming.ClassMemberCase, value: camelBack } + - { key: readability-identifier-naming.PrivateMemberCase, value: camelBack } + - { key: readability-identifier-naming.ProtectedMemberCase, value: camelBack } + - { key: readability-identifier-naming.EnumConstantCase, value: camelBack } + - { key: readability-identifier-naming.ConstexprVariableCase, value: camelBack } + - { key: readability-identifier-naming.GlobalConstantCase, value: camelBack } + - { key: readability-identifier-naming.MemberConstantCase, value: CamelCase } + - { key: readability-identifier-naming.StaticConstantCase, value: camelBack } + - { key: readability-implicit-bool-conversion.AllowIntegerConditions, value: 1 } + - { key: readability-implicit-bool-conversion.AllowPointerConditions, value: 1 } diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9b4b501 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 2 + +[*.py] +indent_size = 4 +indent_style = space + +[{makefile, Makefile}*] +indent_style = tab diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..2acf63f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +a52e90c0419d7da0fe65cb758d2ed10024ec7b2e +06604fd12398b7d1b0563350ea48382555a51840 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..01fa20a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ +## Summary +Describe what your change accomplishes + +## Test plan +Check that OI is working correctly, you can add some unit or integration test, +paste the output of some manual test, and / or paste the output of running +the test locally with `make test-static` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..134d647 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +## OI specific +build/ +test.o +test/tester +test/mttest? +test/mttest2_inline +test/integration_mttest +test/integration_cycles +test/integration_sleepy +test/integration_packed +test/mapiter +test/userDef1 +test/vector +test/inlined_test +test/.autogen-* +oi_preprocessed +*_test.oid +oid_out.json +compile_commands.json +oid_metrics.json +Testing +*.o +PADDING +failed +fb_*_wrapper.sh +website/node_modules + +## Vim +*.swp +*.swo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..81d9eb8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "extern/drgn"] + path = extern/drgn + url = git@github.com:JakeHillion/drgn.git +[submodule "extern/folly"] + path = extern/folly + url = git@github.com:jgkamat/folly.git +[submodule "extern/rocksdb"] + path = extern/rocksdb + url = git@github.com:facebook/rocksdb.git diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..98d82bf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,376 @@ +# object-introspection +cmake_minimum_required(VERSION 3.13) +project(object-introspection) + +# Lets find_program() locate SETUID binaries +cmake_policy(SET CMP0109 NEW) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_compile_options(-ggdb3) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +add_compile_definitions(OSS_ENABLE) + +include(FetchContent) +include(ProcessorCount) +include(cmake/StandardProjectSettings.cmake) +include(cmake/PreventInSourceBuilds.cmake) +include(cmake/CompilerWarnings.cmake) + +option(STATIC_LINK "Statically link oid" OFF) +option(ASAN "Enable address sanitizer" OFF) +option(WITH_TESTS "Build with tests" Off) +option(FORCE_BOOST_STATIC "Build with static boost" On) +option(FORCE_LLVM_STATIC "Build with static llvm and clang" On) + +if (ASAN) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_link_options(-fsanitize=address) +endif() + + + +## System checks +## These checks are potentially fatal so perform them first. + +### Require sudo and setcap (for setting oid capabilities) +find_program(SETCAP setcap) +if(NOT SETCAP) + message(FATAL_ERROR "setcap not found - please install") +endif() + +if(NOT EXISTS "/etc/centos-release") + find_program(SUDO sudo) + if(NOT SUDO) + message(FATAL_ERROR "sudo not found - please install") + endif() +endif() + +### (Re)download submodules +find_package(Git QUIET) + +# TODO: No idea if this huge block is required, just picked from an example. There may be a short-hand. +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") +# Update submodules as needed + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + + # This is a hack. If contents in drgn/libdrgn folder are not found, do a force checkout + # If drgn/* is manually deleted (for whatever reason), git doesn't seem to re-pull the contents unless forced + if (NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/drgn/libdrgn") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive --force + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + else() + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + endif() + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() + endif() +endif() + +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/drgn") + message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") +endif() + +### Select Python version +find_program(PYTHON NAMES python3.8 python3) + + +### gflags (before glog) +find_package(gflags REQUIRED) + +### tomlplusplus (for configuration files) +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git + GIT_TAG 4b166b69f28e70a416a1a04a98f365d2aeb90de8 # v3.2.0 +) +FetchContent_MakeAvailable(tomlplusplus) + +### glog +FetchContent_Declare( + glog + GIT_REPOSITORY https://github.com/google/glog.git + GIT_TAG 96a2f23dca4cc7180821ca5f32e526314395d26a +) +FetchContent_MakeAvailable(glog) + +### bison & flex (for oid_parser) +find_package(BISON 3.5 REQUIRED) +find_package(FLEX) + +### Boost +### Always use static linking with Boost, as some of its dependencies are not in the system's LD_LIBRARY_PATH. +if (FORCE_BOOST_STATIC) + set(Boost_USE_STATIC_LIBS True) +endif() +find_package(Boost REQUIRED COMPONENTS + system + filesystem + thread + regex + serialization +) +message(STATUS "Linking Boost libraries: ${Boost_LIBRARIES}") + +### LLVM and Clang - Preferring Clang 12 +find_package(LLVM 12 REQUIRED CONFIG) +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + +find_package(Clang REQUIRED CONFIG) +message(STATUS "Found Clang ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}") + +### msgpack +# msgpack v3.0.0 doesn't define the msgpackc-cxx target, but since the library is header only, +# we can locate the header dir and add it to our include directories. +# Ideally, we would use a more modern version, like v3.3.0, and directly use the msgpackc-cxx target. +find_package(msgpack REQUIRED CONFIG) +get_target_property(MSGPACK_INCLUDE_DIRS msgpackc INTERFACE_INCLUDE_DIRECTORIES) +include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) + +### folly +### use folly as a header only library. some features won't be supported. +include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/extern/folly) + +### zstd (for rocksdb) +find_package(zstd REQUIRED) + +### rocksdb +add_custom_target(librocksdb ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/extern/rocksdb + COMMAND cmake -G Ninja -B build/ -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=Off -DWITH_LIBURING=Off -DWITH_ZSTD=On + COMMAND cmake --build build/ --target rocksdb + BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/extern/rocksdb/build/librocksdb.a + COMMENT "Building RocksDB" + USES_TERMINAL +) +set(ROCKSDB_PATH "${PROJECT_SOURCE_DIR}/extern/rocksdb") +include_directories(SYSTEM "${ROCKSDB_PATH}/include") + +### drgn +# The setup.py script in drgn is really meant to build drgn (python +# debugger). It shoves the C headers/lib in a temporary directory (which +# is named 'build' below using --build-temp flag). It may(not) make sense +# to just build libdrgn manually. Don't know how finicky the setup.py +# might be. These are the steps to manually build lib/headers and output +# to extern/drgn/libdrgn/build directory :- +# +# cd extern/drgn/libdrgn +# autoreconf -i . +# autoreconf -i ./elfutils +# mkdir build +# cd build +# ../configure +# make +# +# Since setup.py has a single cmd to do this, just use it for now. +# +# Another extemely annoying point. drgn pretty much has to be compiled with gcc only +# clang-12 does NOT work. clang fails with the following error :- +# configure: error: gcc with GNU99 support required + +set(DRGN_CONFIGURE_FLAGS "--with-libkdumpfile=no") +if (ASAN) + list(APPEND DRGN_CONFIGURE_FLAGS "--enable-asan=yes") +endif() +# We always compile drgn with aggressive optimizations because it is +# responsible for most of the time OID takes to generate caches. +set(DRGN_CFLAGS "-O3" "-march=broadwell") +add_custom_target(libdrgn ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/extern/drgn + COMMAND unset BISON_PKGDATADIR && CC=gcc CFLAGS="${DRGN_CFLAGS}" CONFIGURE_FLAGS="${DRGN_CONFIGURE_FLAGS}" ${PYTHON} ./setup.py build --build-temp build + BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/extern/drgn/build/.libs/libdrgnimpl.a + ${CMAKE_CURRENT_SOURCE_DIR}/extern/drgn/build/velfutils/libdw/libdw.a + ${CMAKE_CURRENT_SOURCE_DIR}/extern/drgn/build/velfutils/libelf/libelf.a + ${CMAKE_CURRENT_SOURCE_DIR}/extern/drgn/build/velfutils/libdwelf/libdwelf.a + COMMENT "Building drgn" + USES_TERMINAL +) +set(DRGN_PATH "${PROJECT_SOURCE_DIR}/extern/drgn/build") + +# Ideally drgn stuff should be together at the end. But looks like rpath needs +# to be set before add_executable() unfortunately. Maybe split libdrgn stuff +# into a separate file later. +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_INSTALL_RPATH "${DRGN_PATH}/.libs") +set(CMAKE_BUILD_RPATH "${DRGN_PATH}/.libs") +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +include_directories(SYSTEM "${DRGN_PATH}") + + + +if (STATIC_LINK) + # glog links against the `gflags` target, which is an alias for `gflags_shared` + # For static builds, we force it to link against `gflags_static` instead + set_property(TARGET glog PROPERTY INTERFACE_LINK_LIBRARIES "gflags_static") +endif() + +# FIXME: LLVM 12's source code is not compatible with C++20. +# We should check with the compiler team if we could apply a fix to our LLVM. +# In the meantime, we can compile OICompiler with C++17. +set_source_files_properties(src/OICompiler.cpp PROPERTIES COMPILE_FLAGS -std=c++17 SKIP_PRECOMPILE_HEADERS ON) + + + +## OI Dependencies (linked to by output libraries and executables) +### OI Language Parser +BISON_TARGET(Parser src/OIParser.yy ${CMAKE_CURRENT_BINARY_DIR}/OIParser.tab.cpp + DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/OIParser.tab.hh) +FLEX_TARGET(Lexer src/OILexer.l ${CMAKE_CURRENT_BINARY_DIR}/OILexer.cpp) + +ADD_FLEX_BISON_DEPENDENCY(Lexer Parser) + +add_library(oid_parser STATIC ${BISON_Parser_OUTPUTS} ${FLEX_Lexer_OUTPUTS}) +target_link_libraries(oid_parser glog::glog) + +### Core OI +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) +add_library(oicore + src/ContainerInfo.cpp + src/Descs.cpp + src/FuncGen.cpp + src/Metrics.cpp + src/OICache.cpp + src/OICodeGen.cpp + src/OICompiler.cpp + src/OIUtils.cpp + src/PaddingHunter.cpp + src/Serialize.cpp + src/SymbolService.cpp +) +add_dependencies(oicore libdrgn) +set_project_warnings(oicore) +target_include_directories(oicore SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS}) +target_compile_definitions(oicore PRIVATE ${LLVM_DEFINITIONS}) + +llvm_map_components_to_libnames(llvm_libs core native mcjit x86disassembler) +target_link_libraries(oicore + ${Boost_LIBRARIES} + Boost::headers + glog::glog + tomlplusplus::tomlplusplus +) +if (FORCE_LLVM_STATIC) + target_link_libraries(oicore + clangCodeGen + clangFrontend + ) +else() + target_link_libraries(oicore + clang-cpp + ) +endif() +# link the llvm_libs last as they must come after the clang dependencies in the +# linker order +if (FORCE_LLVM_STATIC) + target_link_libraries(oicore ${llvm_libs}) +else() + target_link_libraries(oicore LLVM) +endif() + +target_link_libraries(oicore + "-L${DRGN_PATH}/.libs" + drgn + dw + pthread +) + +### TreeBuilder +add_library(treebuilder src/TreeBuilder.cpp) +add_dependencies(treebuilder librocksdb) +target_link_libraries(treebuilder + ${ROCKSDB_PATH}/build/librocksdb.a + oicore # overkill but it does need a lot of stuff + zstd::zstd +) + + + +## OI Outputs +### Object Introspection as a Library (OIL) +add_library(oil src/OILibrary.cpp src/OILibraryImpl.cpp) +target_include_directories(oil PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(oil oicore) + +### Object Introspection as a Library Generator (OILGen) +add_executable(oilgen + tools/OILGen.cpp + src/OIGenerator.cpp + src/DrgnUtils.cpp +) +target_link_libraries(oilgen oicore) + +### Object Introspection Compiler +add_executable(oi_compile src/OICompile.cpp) +target_link_libraries(oi_compile oicore oil) + +### Object Introspection cache Printer (OIP) +add_executable(oip tools/OIP.cpp) +set_project_warnings(oip) +target_link_libraries(oip oicore) + +### Object Introspection Tree Builder (OITB) +add_executable(oitb tools/OITB.cpp) +set_project_warnings(oitb) +target_link_libraries(oitb oicore treebuilder) + +### Object Introspection Debugger (OID) +add_executable(oid src/OID.cpp src/OIDebugger.cpp) +set_project_warnings(oid) + +target_link_libraries(oid oicore oid_parser treebuilder) +if (STATIC_LINK) + target_link_libraries(oid gflags_static) +else() + target_link_libraries(oid gflags_shared) +endif() +target_link_libraries(oid oicore treebuilder) + +add_custom_command(TARGET oid POST_BUILD + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND sudo setcap cap_sys_ptrace+ep ./oid) + +### Object Introspection Tests +if (WITH_TESTS) + add_subdirectory(test) +endif() + + + +### Custom link options +if (STATIC_LINK) + target_link_libraries(oicore -static) + target_link_libraries(oil -static) + target_link_libraries(oip -static) + target_link_libraries(oid -static) + target_link_libraries(oitb -static) +endif() + + + +## Performance improvements +### Precompile headers +target_precompile_headers(oicore + PUBLIC + PUBLIC + PUBLIC + PUBLIC + PUBLIC + PUBLIC + PUBLIC + PUBLIC +) + +foreach(TARGET oil oip oi_compile oid oitb) + target_precompile_headers(${TARGET} REUSE_FROM oicore) +endforeach() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4bd525a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f2c9f82 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to object-introspection +We want to make contributing to this project as easy and transparent as +possible. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## License +By contributing to object-introspection, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.org b/README.org new file mode 100644 index 0000000..3225a77 --- /dev/null +++ b/README.org @@ -0,0 +1,14 @@ + +* object-introspection + +[[https://matrix.to/#/#object-introspection:matrix.org][https://img.shields.io/matrix/object-introspection:matrix.org.svg]] + +Object Introspection is a memory profiling technology for C++ objects. It provides the ability to dynamically instrument applications to capture the precise memory occupancy of entire object hierarchies including all containers and dynamic allocations. All this with no code modification or recompilation! + +For more information on the technology and how to get started applying it to your applications please check out the [[Object Introspection][https://object-introspection.com]] website. + +* Join the Object Introspection community +See the [[CONTRIBUTING][CONTRIBUTING.md]] file for how to help out. + +* License +Object Introspection is licensed under the [[Apache 2.0 License][LICENSE]]. diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..c968140 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,84 @@ +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md + +function(set_project_warnings project_name) + option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) + + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + + # We use "old style cast" a lot through our code base, this warning is therefore too noisy. + -Wno-old-style-cast + # Sign conversion warnings don't seem to point to error and is noisy. Let's revisit it later! + -Wno-sign-conversion # warn on sign conversions + + -Werror=unused-result # turn unused [[nodiscard]] into errors + ) + + if(WARNINGS_AS_ERRORS) + set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) + set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) + endif() + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + + if(MSVC) + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + target_compile_options(${project_name} PUBLIC ${PROJECT_WARNINGS}) + +endfunction() diff --git a/cmake/FindThrift.cmake b/cmake/FindThrift.cmake new file mode 100644 index 0000000..1030e5a --- /dev/null +++ b/cmake/FindThrift.cmake @@ -0,0 +1,24 @@ +# - Try to find Thrift +# +# Defines the following variables: +# +# THRIFT_FOUND - system has Thrift +# THRIFT_COMPILER - The thrift compiler executable +# THRIFT_INCLUDE_DIRS - The Thrift include directory + +find_program(THRIFT_COMPILER thrift) + +find_path(THRIFT_INCLUDE_DIRS + NAMES + thrift/annotation/cpp.thrift + HINTS + ${THRIFT_ROOT}) + +include (FindPackageHandleStandardArgs) + +# handle the QUIETLY and REQUIRED arguments and set THRIFT_FOUND to TRUE if all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Thrift "Thrift not found" + THRIFT_COMPILER + THRIFT_INCLUDE_DIRS) + +mark_as_advanced(THRIFT_COMPILER THRIFT_INCLUDE_DIRS) diff --git a/cmake/Findzstd.cmake b/cmake/Findzstd.cmake new file mode 100644 index 0000000..69b4132 --- /dev/null +++ b/cmake/Findzstd.cmake @@ -0,0 +1,29 @@ +# - Find zstd +# Find the zstd compression library and includes +# +# zstd_INCLUDE_DIRS - where to find zstd.h, etc. +# zstd_LIBRARIES - List of libraries when using zstd. +# zstd_FOUND - True if zstd found. + +find_path(zstd_INCLUDE_DIRS zstd.h HINTS ${zstd_ROOT_DIR}/include) + +# Don't merge these two `find_library`! This is to give priority to the static library. +# See "Professional CMake", page 300, for more info. +find_library(zstd_LIBRARIES libzstd.a HINTS ${zstd_ROOT_DIR}/lib) +find_library(zstd_LIBRARIES zstd HINTS ${zstd_ROOT_DIR}/lib) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(zstd DEFAULT_MSG zstd_LIBRARIES zstd_INCLUDE_DIRS) + +mark_as_advanced( + zstd_LIBRARIES + zstd_INCLUDE_DIRS) + +if(zstd_FOUND AND NOT (TARGET zstd::zstd)) + add_library (zstd::zstd UNKNOWN IMPORTED) + set_target_properties(zstd::zstd + PROPERTIES + IMPORTED_LOCATION ${zstd_LIBRARIES} + INTERFACE_INCLUDE_DIRECTORIES ${zstd_INCLUDE_DIRS} + COMPILE_DEFINITIONS -DZSTD) +endif() diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 0000000..57d9c59 --- /dev/null +++ b/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,18 @@ +# +# This function will prevent in-source builds +function(AssureOutOfSourceBuilds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +assureoutofsourcebuilds() diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 0000000..77519bb --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,42 @@ +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo") +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) + +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported( + RESULT + result + OUTPUT + output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + else() + message(SEND_ERROR "IPO is not supported: ${output}") + endif() +endif() +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + add_compile_options(-fcolor-diagnostics) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + add_compile_options(-fdiagnostics-color=auto) +else() + message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") +endif() + diff --git a/dev.oid.toml b/dev.oid.toml new file mode 100644 index 0000000..70f7c77 --- /dev/null +++ b/dev.oid.toml @@ -0,0 +1,48 @@ +# DON'T use this file directly. +# +# It is used to extend sample.oid.toml for development purposes. + +[types] +containers = [ + "PWD/types/array_type.toml", + "PWD/types/string_type.toml", + "PWD/types/cxx11_string_type.toml", + "PWD/types/folly_iobuf_type.toml", + "PWD/types/folly_iobuf_queue_type.toml", + "PWD/types/set_type.toml", + "PWD/types/unordered_set_type.toml", + "PWD/types/seq_type.toml", + "PWD/types/list_type.toml", + "PWD/types/cxx11_list_type.toml", + "PWD/types/deque_list_type.toml", + "PWD/types/shrd_ptr_type.toml", + "PWD/types/uniq_ptr_type.toml", + "PWD/types/std_map_type.toml", + "PWD/types/std_unordered_map_type.toml", + "PWD/types/pair_type.toml", + "PWD/types/stack_container_adapter_type.toml", + "PWD/types/queue_container_adapter_type.toml", + "PWD/types/priority_queue_container_adapter_type.toml", + "PWD/types/ref_wrapper_type.toml", + "PWD/types/multi_map_type.toml", + "PWD/types/folly_small_heap_vector_map.toml", + "PWD/types/folly_optional_type.toml", + "PWD/types/optional_type.toml", + "PWD/types/try_type.toml", + "PWD/types/fb_string_type.toml", + "PWD/types/small_vec_type.toml", + "PWD/types/f14_fast_map.toml", + "PWD/types/f14_node_map.toml", + "PWD/types/f14_vector_map.toml", + "PWD/types/f14_fast_set.toml", + "PWD/types/f14_node_set.toml", + "PWD/types/f14_vector_set.toml", + "PWD/types/sorted_vec_set_type.toml", + "PWD/types/map_seq_type.toml", + "PWD/types/boost_bimap_type.toml", + "PWD/types/repeated_field_type.toml", + "PWD/types/repeated_ptr_field_type.toml", + "PWD/types/caffe2_blob_type.toml", + "PWD/types/std_variant.toml", + "PWD/types/thrift_isset_type.toml", +] diff --git a/examples/compile-time-oil/.gitignore b/examples/compile-time-oil/.gitignore new file mode 100644 index 0000000..2a0a8a5 --- /dev/null +++ b/examples/compile-time-oil/.gitignore @@ -0,0 +1,6 @@ +oilgen +OilVectorOfStrings +OilVectorOfStrings.o +jit.cpp +JitCompiled.o + diff --git a/examples/compile-time-oil/Makefile b/examples/compile-time-oil/Makefile new file mode 100644 index 0000000..f153606 --- /dev/null +++ b/examples/compile-time-oil/Makefile @@ -0,0 +1,28 @@ +.DEFAULT_GOAL := all + +CXX=clang++ +CXXFLAGS=-g -std=c++17 -Wall -pedantic -DOIL_AOT_COMPILATION=1 + +.PHONY: oilgen + +INC=-I../../include + +OilVectorOfStrings.o: OilVectorOfStrings.cpp + ${CXX} ${CXXFLAGS} ${INC} OilVectorOfStrings.cpp -c -o OilVectorOfStrings.o + +oilgen: + rm -f oilgen + (cd ../../ && make oid-devel) + ln -s ../../build/oilgen oilgen + +JitCompiled.o: oilgen OilVectorOfStrings.o + DRGN_ENABLE_TYPE_ITERATOR=1 ./oilgen -o JitCompiled.o -c ../../build/sample.oid.toml -d OilVectorOfStrings.o + +OilVectorOfStrings: OilVectorOfStrings.o JitCompiled.o + ${CXX} ${CXXFLAGS} OilVectorOfStrings.o JitCompiled.o -o OilVectorOfStrings + +all: OilVectorOfStrings + +clean: + rm -f oilgen OilVectorOfStrings{,.o,.o.dwarf} JitCompiled.o jit.cpp + diff --git a/examples/compile-time-oil/OilVectorOfStrings.cpp b/examples/compile-time-oil/OilVectorOfStrings.cpp new file mode 100644 index 0000000..0fb29a3 --- /dev/null +++ b/examples/compile-time-oil/OilVectorOfStrings.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +#include +#include +#include + +struct Foo { + std::vector strings; +}; + +int main() { + Foo foo; + + foo.strings.push_back("Lorem ipsum dolor"); + foo.strings.push_back("sit amet,"); + foo.strings.push_back("consectetur adipiscing elit,"); + + size_t size = -1; + int ret = ObjectIntrospection::getObjectSize(&foo, &size); + + std::cout << "oil returned: " << ret << "; with size: " << size << std::endl; +} diff --git a/examples/oil-metrics/Metrics.cpp b/examples/oil-metrics/Metrics.cpp new file mode 100644 index 0000000..0503f36 --- /dev/null +++ b/examples/oil-metrics/Metrics.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "Metrics.h" + +namespace Metrics { +std::atomic Metrics::singleton = nullptr; + +const char *to_string(ArgTiming t) { + switch (t) { + case ENTRY: + return "entry"; + case EXIT: + return "exit"; + default: + return ""; + } +} + +Metrics::Metrics(ObjectIntrospection::options opts, const std::string &savePath) + : opts(opts) { + writer = std::fstream(savePath, std::ios_base::out); + writer << "{ \"metrics\": [" << std::endl; + + // set the singleton to this once fully constructed + Metrics::singleton = this; +} + +Metrics::~Metrics() { + writer << "]}" << std::endl; +} + +void Metrics::save(std::string object) { + Metrics *m = singleton.load(); + std::lock_guard guard(m->writerLock); + + if (m->hasWritten) { + m->writer << ',' << object << std::endl; + } else { + m->hasWritten = true; + m->writer << object << std::endl; + } +} + +void Metrics::saveArg(const char *name, const char *argName, ArgTiming timing, + size_t size) { + std::string out = "{\"type\": \"size\", \"traceName\": \""; + out += name; + out += "\", \"argName\": \""; + out += argName; + out += "\", \"timing\": \""; + out += to_string(timing); + out += "\", \"size\": "; + out += std::to_string(size); + out += '}'; + + save(out); +} + +void Metrics::saveDuration(const char *name, + std::chrono::milliseconds duration) { + std::string out = "{\"type\": \"duration\", \"traceName\": \""; + out += name; + out += "\", \"duration\": "; + out += std::to_string(duration.count()); + out += '}'; + + save(out); +} + +Tracing::Tracing(const char *name, bool enabled) + : name(name), enabled(enabled) { +} + +Tracing::~Tracing() { + if (isTimingEnabled()) { + auto duration = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - startTime); + saveDuration(duration); + } + + for (auto const &exitFunc : exitFuncs) + exitFunc(); +} + +bool Tracing::isTimingEnabled() { + return enabled || Metrics::isEnabled(); +} + +bool Tracing::isArgEnabled(const char *argName, ArgTiming timing) { + return enabled || Metrics::isEnabled(); +} + +void Tracing::start() { + if (isTimingEnabled()) { + startTime = std::chrono::high_resolution_clock::now(); + } +} + +void Tracing::saveArg(const char *argName, ArgTiming timing, size_t size) { + Metrics::saveArg(name, argName, timing, size); +} + +void Tracing::saveDuration(std::chrono::milliseconds duration) { + Metrics::saveDuration(name, duration); +} +} // namespace Metrics diff --git a/examples/oil-metrics/Metrics.h b/examples/oil-metrics/Metrics.h new file mode 100644 index 0000000..0815baa --- /dev/null +++ b/examples/oil-metrics/Metrics.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +#include +#include +#include +#include +#include +#include +#include + +namespace Metrics { +enum ArgTiming { + ENTRY, + EXIT, +}; + +class Metrics { + friend class Tracing; + + public: + Metrics(ObjectIntrospection::options opts, const std::string &savePath); + ~Metrics(); + + void enable() { + enableAll = true; + } + + private: + static std::atomic singleton; + + ObjectIntrospection::options opts; + std::fstream writer; + std::mutex writerLock; + bool hasWritten = false; + bool enableAll = false; + + static ObjectIntrospection::options &getOptions() { + return singleton.load()->opts; + } + + static bool isEnabled() { + return singleton.load()->enableAll; + } + + static void save(std::string object); + static void saveArg(const char *name, const char *argName, ArgTiming timing, + size_t size); + static void saveDuration(const char *name, + std::chrono::milliseconds duration); +}; + +class Tracing { + public: + Tracing(const char *name, bool enabled = false); + ~Tracing(); + + void start(); + + template + void registerArg(const char *argName, T *value); + + private: + bool isTimingEnabled(); + bool isArgEnabled(const char *argName, ArgTiming timing); + void saveArg(const char *argName, ArgTiming timing, size_t size); + void saveDuration(std::chrono::milliseconds duration); + + template + void inspectArg(const char *argName, ArgTiming timing, T *value); + + const char *name; + bool enabled; + std::chrono::high_resolution_clock::time_point startTime; + std::vector> exitFuncs; +}; + +template +void Tracing::registerArg(const char *argName, T *value) { + if (isArgEnabled(argName, ArgTiming::ENTRY)) { + inspectArg(argName, ArgTiming::ENTRY, value); + } + + if (isArgEnabled(argName, ArgTiming::EXIT)) { + if (exitFuncs.capacity() == 0) + exitFuncs.reserve(8); + + std::function exitFunc = [this, argName, value]() { + inspectArg(argName, ArgTiming::EXIT, value); + }; + + exitFuncs.push_back(exitFunc); + } +} + +template +void Tracing::inspectArg(const char *argName, ArgTiming timing, T *value) { + size_t size; + if (int responseCode = ObjectIntrospection::getObjectSize( + value, &size, Metrics::getOptions(), false); + responseCode > ObjectIntrospection::Response::OIL_INITIALISING) { + throw std::runtime_error("object introspection failed"); + } else if (responseCode == ObjectIntrospection::Response::OIL_INITIALISING) { + return; // do nothing to avoid blocking + } + + saveArg(argName, timing, size); +} +} // namespace Metrics diff --git a/examples/web/AddrBook/AddrBook.cpp b/examples/web/AddrBook/AddrBook.cpp new file mode 100644 index 0000000..3ac072b --- /dev/null +++ b/examples/web/AddrBook/AddrBook.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include + +class Contact { + public: + Contact(std::string& f, std::string& l, std::string& n) { + firstName = f; + lastName = l; + number = n; + }; + std::string getFirstName() { + return firstName; + }; + std::string getLastName() { + return lastName; + }; + std::string getNumber() { + return number; + }; + + private: + std::string firstName, lastName; + std::string number; +}; + +class AddressBook { + public: + void AddContact(std::string& f, std::string& l, std::string& n) { + Entries.insert(Entries.begin(), Contact(f, l, n)); + }; + + void DumpContacts(void) { + int sz = 0; + std::cout << "number of Entries: " << Entries.size() << std::endl; + for (auto contact : Entries) { + std::string firstName = contact.getFirstName(); + std::string lastName = contact.getLastName(); + std::string number = contact.getNumber(); + + sz += + sizeof(contact) + firstName.size() + lastName.size() + number.size(); + + std::cout << "sizeof contact = " << sizeof(contact) << " sizeof fname " + << sizeof(firstName) << " sizeof lname: " << sizeof(lastName) + << " sizeof number: " << sizeof(number) + << " size fname: " << firstName.size() + << " size lname: " << lastName.size() + << " size number: " << number.size() << std::endl; + } + std::cout << "Total size = " << sz << " bytes\n\n"; + }; + + private: + int rev; + std::string Owner; + std::vector Entries; +}; + +/* + * Borrowed from + * https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c + * . + */ +std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + const size_t maxIndex = (sizeof(charset) - 1); + return charset[rand() % maxIndex]; + }; + + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +AddressBook globalAddrBook; + +int main(int argc, char* argv[]) { + AddressBook A; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 100); + + while (1) { + std::string fname = random_string(distr(gen)); + std::string sname = random_string(distr(gen)); + std::string number = random_string(distr(gen)); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + A.AddContact(fname, sname, number); + A.DumpContacts(); + } +} diff --git a/examples/web/AddrBook/Makefile b/examples/web/AddrBook/Makefile new file mode 100644 index 0000000..ec50a53 --- /dev/null +++ b/examples/web/AddrBook/Makefile @@ -0,0 +1,16 @@ +.DEFAULT_GOAL := addrbook +CC = clang++ +CFLAGS = -std=c++20 -g -O3 +SRC = AddrBook.cpp + +default: addrbook + + +.PHONY: addrbook +addrbook: $(SRC) + $(CC) -o $@ $^ $(CFLAGS) + +.PHONY: clean + +clean: + rm addrbook diff --git a/extern/drgn b/extern/drgn new file mode 160000 index 0000000..204c257 --- /dev/null +++ b/extern/drgn @@ -0,0 +1 @@ +Subproject commit 204c257b26788f0dd3913486bbb16f85053db0fd diff --git a/extern/folly b/extern/folly new file mode 160000 index 0000000..d247a1a --- /dev/null +++ b/extern/folly @@ -0,0 +1 @@ +Subproject commit d247a1ab1891677bfc8dd4fd2ea95fb43e160455 diff --git a/extern/rocksdb b/extern/rocksdb new file mode 160000 index 0000000..3981430 --- /dev/null +++ b/extern/rocksdb @@ -0,0 +1 @@ +Subproject commit 3981430f541098982b2e39d85c6f12fe0d6a5bdf diff --git a/include/ObjectIntrospection.h b/include/ObjectIntrospection.h new file mode 100644 index 0000000..0c23031 --- /dev/null +++ b/include/ObjectIntrospection.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include + +/* + * Library interface for Object Introspection + * + * On the first call for each type these library functions perform significant + * setup. In a single-threaded context, the calling thread blocks on the first + * call. In a multi-threaded context, the first caller blocks, and other callers + * see Response::OIL_INITIALISING until initialisation completes. + * + * The options passed to library functions MUST NOT change after the first call + * for each type. By default, this will result in Response::OIL_CHANGED_OPTIONS. + * This check can be disabled for decreased latency by passing checkOptions as + * false. + * + * Generally the only required option is configFilePath. See sample.oid.toml for + * an example configuration file. + * + * -- SINGLE-THREADED + * ObjectIntrospection::options opts = { .configFilePath = "sample.oid.toml" }; + * size_t size; + * int responseCode = ObjectIntrospection::getObjectSize(&obj, &size, opts); + * if (responseCode != ObjectIntrospection::Response::OIL_SUCCESS) { + * // handle error + * } + * + * -- MULTI-THREADED (NO SETUP) + * ObjectIntrospection::options opts = { .configFilePath = "sample.oid.toml" }; + * size_t size; + * int responseCode = ObjectIntrospection::getObjectSize(&obj, &size, opts); + * if (responseCode > ObjectIntrospection::Response::OIL_INITIALISING) { + * // handle error + * } else if (responseCode == ObjectIntrospection::Response::OIL_SUCCESS) { + * // do something + * } // do nothing if still initialising + * + * -- MULTI-THREADED (WITH SETUP) + * ObjectIntrospection::options opts = { .configFilePath = "sample.oid.toml" }; + * int responseCode = ObjectIntrospection::CodegenHandler::init(opts); + * if (responseCode != ObjectIntrospection::Response::OIL_SUCCESS) { + * // handle error + * } + * size_t size; + * int responseCode = ObjectIntrospection::getObjectSize(&obj, &size); + * if (responseCode == ObjectIntrospection::Response::OIL_UNINITIALISED) { + * // handle error - impossible if successfully inited + * } + * + */ +namespace ObjectIntrospection { + +enum Response : int { + OIL_SUCCESS = 0, + OIL_INITIALISING = 1, + OIL_CHANGED_OPTIONS = 2, + OIL_BAD_CONFIG_FILE = 3, + OIL_SEGMENT_INIT_FAIL = 4, + OIL_COMPILATION_FAILURE = 5, + OIL_RELOCATION_FAILURE = 6, + OIL_UNINITIALISED = 7, +}; + +struct options { + std::string configFilePath{}; + std::string debugFilePath{}; + std::string cacheDirPath{}; + + int debugLevel = 0; + std::string sourceFileDumpPath{}; + + bool layout = false; + bool chaseRawPointers = false; + bool generateJitDebugInfo = false; + + bool enableUpload = false; + bool enableDownload = false; + bool abortOnLoadFail = false; + bool forceJIT = true; + + friend bool operator==(const options &lhs, const options &rhs); + friend bool operator!=(const options &lhs, const options &rhs); +}; + +constexpr std::string_view OI_SECTION_PREFIX = ".oi."; + +class OILibrary { + friend class OILibraryImpl; + + public: + OILibrary(void *TemplateFunc, options opt); + ~OILibrary(); + int init(); + int getObjectSize(void *ObjectAddr, size_t *size); + + options opts; + + private: + class OILibraryImpl *pimpl_; + + size_t (*fp)(void *) = nullptr; +}; + +template +class CodegenHandler { + public: + static int init(const options &opts = {}, bool checkOptions = true) { + OILibrary *lib; + return getLibrary(lib, opts, checkOptions); + } + + static void teardown() { + OILibrary *lib; + if (int responseCode = getLibrary(lib); + responseCode != Response::OIL_SUCCESS) { + return; + } + + getLib()->store(nullptr); + getBoxedLib()->store(nullptr); + delete lib; + } + + static int getObjectSize(T *ObjectAddr, size_t *ObjectSize) { + OILibrary *lib; + if (int responseCode = getLibrary(lib); + responseCode != Response::OIL_SUCCESS) { + return responseCode; + } + + return lib->getObjectSize((void *)ObjectAddr, ObjectSize); + } + + static int getObjectSize(T *ObjectAddr, size_t *ObjectSize, + const options &opts, bool checkOptions = true) { + OILibrary *lib; + if (int responseCode = getLibrary(lib, opts, checkOptions); + responseCode != Response::OIL_SUCCESS) { + return responseCode; + } + + return lib->getObjectSize((void *)ObjectAddr, ObjectSize); + } + + private: + static std::atomic *getLib() { + static std::atomic lib = nullptr; + return &lib; + } + + static std::atomic *> *getBoxedLib() { + static std::atomic *> boxedLib = nullptr; + return &boxedLib; + } + + static int getLibrary(OILibrary *&result) { + std::atomic *curBoxedLib = getBoxedLib()->load(); + if (curBoxedLib == nullptr) + return Response::OIL_UNINITIALISED; + + OILibrary *curLib = curBoxedLib->load(); + if (curLib == nullptr) + return Response::OIL_UNINITIALISED; + + result = curLib; + return Response::OIL_SUCCESS; + } + + static int getLibrary(OILibrary *&result, const options &opts, + bool checkOptions) { + std::atomic *curBoxedLib = getBoxedLib()->load(); + + if (curBoxedLib == nullptr) { + if (!getBoxedLib()->compare_exchange_strong(curBoxedLib, getLib())) { + return Response::OIL_INITIALISING; + } + curBoxedLib = getLib(); + + int (*sizeFp)(T *, size_t *) = &getObjectSize; + void *typedFp = reinterpret_cast(sizeFp); + OILibrary *newLib = new OILibrary(typedFp, opts); + + if (int initCode = newLib->init(); initCode != Response::OIL_SUCCESS) { + delete newLib; + getBoxedLib()->store(nullptr); // allow next attempt to initialise + return initCode; + } + + getLib()->store(newLib); + } + + OILibrary *curLib = curBoxedLib->load(); + if (curLib == nullptr) { + return Response::OIL_INITIALISING; + } + + if (checkOptions && opts != curLib->opts) { + return Response::OIL_CHANGED_OPTIONS; + } + + result = curLib; + return Response::OIL_SUCCESS; + } +}; + +/* + * Call this from anywhere in your program. It blocks on the first call for + * each type when seen for the first time. Usage patterns are given at the + * top of this file. This method should not be called when utilising + * Ahead-Of-Time (AOT) compilation. + */ +template +int getObjectSize(T *ObjectAddr, size_t *ObjectSize, const options &opts, + bool checkOptions = true) { + return CodegenHandler::getObjectSize(ObjectAddr, ObjectSize, opts, + checkOptions); +} + +/* + * You may only call this after a call to the previous signature, or a + * call to CodegenHandler::init(...) for the used type. + * + * As we can choose to compile the OIL blob Ahead-Of-Time (AOT) rather + * than Just-In-Time (JIT), this default is provided as a weak symbol. + * When in AOT mode this will no-op, removing the burden of JIT on a + * production system. + */ +template +int __attribute__((weak)) getObjectSize(T *ObjectAddr, size_t *ObjectSize) { +#ifdef OIL_AOT_COMPILATION + return Response::OIL_UNINITIALISED; +#else + return CodegenHandler::getObjectSize(ObjectAddr, ObjectSize); +#endif +} + +} // namespace ObjectIntrospection diff --git a/oss.oid.toml b/oss.oid.toml new file mode 100644 index 0000000..8178376 --- /dev/null +++ b/oss.oid.toml @@ -0,0 +1,9 @@ +[headers] +user_paths = [ + "/usr/lib64/llvm12/lib/clang/12.0.1/include/", +] +system_paths = [ + "/usr/include/c++/11", + "/usr/include/c++/11/x86_64-redhat-linux/", + "/usr/include", +] diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..7d6d471 --- /dev/null +++ b/src/Common.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include + +extern "C" { +#include +} + +constexpr int oidMagicId = 0x01DE8; + +struct ContainerInfo; + +struct RootInfo { + std::string varName; + struct drgn_qualified_type type; +}; + +struct ClassMember { + std::string typeName; + std::string varName; +}; + +struct DrgnClassMemberInfo { + struct drgn_type *type; + std::string member_name; + uint64_t bit_offset; + uint64_t bit_field_size; + bool isStubbed; +}; + +struct TypeHierarchy { + std::map> + classMembersMap; + std::map>> + containerTypeMap; + std::map typedefMap; + std::map sizeMap; + std::set knownDummyTypeList; + std::map pointerToTypeMap; + std::set thriftIssetStructTypes; +}; diff --git a/src/ContainerInfo.cpp b/src/ContainerInfo.cpp new file mode 100644 index 0000000..75b9924 --- /dev/null +++ b/src/ContainerInfo.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "ContainerInfo.h" + +#include +#include + +#include + +ContainerTypeEnum containerTypeEnumFromStr(std::string& str) { + static const std::map nameMap = { +#define X(name) {#name, name}, + LIST_OF_CONTAINER_TYPES +#undef X + }; + + if (!nameMap.contains(str)) { + return UNKNOWN_TYPE; + } + return nameMap.at(str); +} + +const char* containerTypeEnumToStr(ContainerTypeEnum ty) { + switch (ty) { +#define X(name) \ + case name: \ + return #name; + LIST_OF_CONTAINER_TYPES +#undef X + + default: + return "UNKNOWN_TYPE"; + } +} + +std::unique_ptr ContainerInfo::loadFromFile( + const fs::path& path) { + toml::table container; + try { + container = toml::parse_file(std::string(path)); + } catch (const toml::parse_error& ex) { + LOG(ERROR) << "ContainerInfo::loadFromFile: " << path << " : " + << ex.description(); + return nullptr; + } + + toml::table* info = container["info"].as_table(); + if (!info) { + LOG(ERROR) << "a container info file requires an `info` table"; + return nullptr; + } + + std::string typeName; + if (std::optional str = + (*info)["typeName"].value()) { + typeName = std::move(*str); + } else { + LOG(ERROR) << "`info.typeName` is a required field"; + return nullptr; + } + + std::optional numTemplateParams = + (*info)["numTemplateParams"].value(); + + ContainerTypeEnum ctype; + if (std::optional str = (*info)["ctype"].value()) { + ctype = containerTypeEnumFromStr(*str); + if (ctype == UNKNOWN_TYPE) { + LOG(ERROR) << "`" << (*str) << "` is not a valid container type"; + return nullptr; + } + } else { + LOG(ERROR) << "`info.ctype` is a required field"; + return nullptr; + } + + std::string header; + if (std::optional str = (*info)["header"].value()) { + header = std::move(*str); + } else { + LOG(ERROR) << "`info.header` is a required field"; + return nullptr; + } + + std::vector ns; + if (toml::array* arr = (*info)["ns"].as_array()) { + ns.reserve(arr->size()); + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + ns.emplace_back(el); + } + }); + } + + std::vector replaceTemplateParamIndex{}; + if (toml::array* arr = (*info)["replaceTemplateParamIndex"].as_array()) { + replaceTemplateParamIndex.reserve(arr->size()); + arr->for_each([&](auto&& el) { + if constexpr (toml::is_integer) { + replaceTemplateParamIndex.push_back(*el); + } + }); + } + + std::optional allocatorIndex = + (*info)["allocatorIndex"].value(); + std::optional underlyingContainerIndex = + (*info)["underlyingContainerIndex"].value(); + + return std::unique_ptr(new ContainerInfo{ + std::move(typeName), + numTemplateParams, + ctype, + std::move(header), + std::move(ns), + std::move(replaceTemplateParamIndex), + allocatorIndex, + underlyingContainerIndex, + }); +} diff --git a/src/ContainerInfo.h b/src/ContainerInfo.h new file mode 100644 index 0000000..28ddd4b --- /dev/null +++ b/src/ContainerInfo.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include + +namespace fs = std::filesystem; + +#define LIST_OF_CONTAINER_TYPES \ + X(UNKNOWN_TYPE) \ + X(ARRAY_TYPE) \ + X(SMALL_VEC_TYPE) \ + X(SET_TYPE) \ + X(UNORDERED_SET_TYPE) \ + X(SEQ_TYPE) \ + X(LIST_TYPE) \ + X(STD_MAP_TYPE) \ + X(STD_UNORDERED_MAP_TYPE) \ + X(MAP_SEQ_TYPE) \ + X(BY_MULTI_QRT_TYPE) \ + X(F14_MAP) \ + X(F14_SET) \ + X(FEED_QUICK_HASH_SET) \ + X(FEED_QUICK_HASH_MAP) \ + X(RADIX_TREE_TYPE) \ + X(PAIR_TYPE) \ + X(STRING_TYPE) \ + X(FOLLY_IOBUF_TYPE) \ + X(FOLLY_IOBUFQUEUE_TYPE) \ + X(FB_STRING_TYPE) \ + X(UNIQ_PTR_TYPE) \ + X(SHRD_PTR_TYPE) \ + X(FB_HASH_MAP_TYPE) \ + X(FB_HASH_SET_TYPE) \ + X(FOLLY_OPTIONAL_TYPE) \ + X(OPTIONAL_TYPE) \ + X(TRY_TYPE) \ + X(REF_WRAPPER_TYPE) \ + X(SORTED_VEC_SET_TYPE) \ + X(REPEATED_FIELD_TYPE) \ + X(CAFFE2_BLOB_TYPE) \ + X(MULTI_MAP_TYPE) \ + X(FOLLY_SMALL_HEAP_VECTOR_MAP) \ + X(CONTAINER_ADAPTER_TYPE) \ + X(MICROLIST_TYPE) \ + X(ENUM_MAP_TYPE) \ + X(BOOST_BIMAP_TYPE) \ + X(STD_VARIANT_TYPE) \ + X(THRIFT_ISSET_TYPE) + +enum ContainerTypeEnum { +#define X(name) name, + LIST_OF_CONTAINER_TYPES +#undef X +}; +ContainerTypeEnum containerTypeEnumFromStr(std::string &str); +const char *containerTypeEnumToStr(ContainerTypeEnum ty); + +struct ContainerInfo { + std::string typeName; + std::optional numTemplateParams; + ContainerTypeEnum ctype = UNKNOWN_TYPE; + std::string header; + std::vector ns; + std::vector replaceTemplateParamIndex{}; + std::optional allocatorIndex{}; + // Index of underlying container in template parameters for a container + // adapter + std::optional underlyingContainerIndex{}; + + static std::unique_ptr loadFromFile(const fs::path &path); + + bool operator<(const ContainerInfo &rhs) const { + return (typeName < rhs.typeName); + } +}; diff --git a/src/Descs.cpp b/src/Descs.cpp new file mode 100644 index 0000000..d824b22 --- /dev/null +++ b/src/Descs.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "Descs.h" + +#include + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +std::ostream &operator<<(std::ostream &os, const FuncDesc::Range &r) { + return os << (void *)r.start << ':' << (void *)r.end; +} + +/* + * Given a register set return the address where the supplied argument + * position can be found at the given pc (what about if we don't have this + * location?). + */ +std::optional FuncDesc::Arg::findAddress( + struct user_regs_struct *regs, uintptr_t pc) const { + auto prevRip = std::exchange(regs->rip, pc); + BOOST_SCOPE_EXIT_ALL(&) { + regs->rip = prevRip; + }; + + struct drgn_object object {}; + BOOST_SCOPE_EXIT_ALL(&) { + drgn_object_deinit(&object); + }; + + if (auto *err = drgn_object_locate(&locator, regs, &object)) { + LOG(ERROR) << "Error while finding address of argument: " << err->message; + drgn_error_destroy(err); + return std::nullopt; + } + + return object.address; +} + +std::optional FuncDesc::getArgumentIndex(const std::string &arg, + bool validateIndex) const { + if (arg == "retval") { + return std::nullopt; + } + + if (arg == "this") { + if (!isMethod) { + LOG(ERROR) << "Function " << symName << " has no 'this' parameter"; + return std::nullopt; + } + return 0; + } + // + // Extract arg's number + auto it = arg.find_first_of("0123456789"); + if (it == std::string::npos) { + LOG(ERROR) << "Invalid argument: " << arg; + return std::nullopt; + } + + const auto *argIdxBegin = arg.data() + it; + const auto *argIdxEnd = arg.data() + arg.size(); + + uint8_t argIdx = 0; + if (auto res = std::from_chars(argIdxBegin, argIdxEnd, argIdx); + res.ec != std::errc{}) { + LOG(ERROR) << "Failed to convert " << arg + << " digits: " << strerror((int)res.ec); + return std::nullopt; + } + + // Check and offset for methods + if (validateIndex && argIdx >= numArgs()) { + LOG(ERROR) << "Argument index " << (int)argIdx + << " too large. Args count: " << numArgs(); + return std::nullopt; + } + + if (isMethod) { + argIdx += 1; + } + + return argIdx; +} + +std::shared_ptr FuncDesc::getArgument( + const std::string &arg) { + std::shared_ptr outArg; + + if (arg == "retval") { + outArg = retval; + } else { + auto argIdx = getArgumentIndex(arg); + if (!argIdx.has_value()) { + return nullptr; + } + + outArg = arguments[*argIdx]; + } + + if (!outArg || !outArg->valid) { + LOG(ERROR) << "Argument " << arg << " for " << symName << " is invalid"; + return nullptr; + } + + return outArg; +} diff --git a/src/Descs.h b/src/Descs.h new file mode 100644 index 0000000..1ea2e77 --- /dev/null +++ b/src/Descs.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include + +extern "C" { +#include +} + +struct FuncDesc { + struct TargetObject; + struct Arg; + struct Retval; + + std::string symName{}; + + struct Range { + uintptr_t start; + uintptr_t end; + + Range() = default; + Range(uintptr_t _start, uintptr_t _end) : start{_start}, end{_end} { + assert(end >= start); + }; + + constexpr uintptr_t size() const noexcept { + return end - start; + } + }; + + std::vector ranges; + std::vector> arguments{}; + std::shared_ptr retval{}; + bool isMethod{false}; + + FuncDesc() = default; + FuncDesc(std::string func) : symName{std::move(func)} {}; + + std::shared_ptr addArgument() { + return arguments.emplace_back(std::make_shared()); + } + + std::shared_ptr getThis() { + if (!isMethod) { + return nullptr; + } + return arguments[0]; + } + + std::shared_ptr getArgument(size_t argPos) { + // Offset by 1, as methods have 'this' at arg 0 + if (isMethod) { + argPos += 1; + } + return arguments[argPos]; + } + + std::shared_ptr getArgument(const std::string &); + std::optional getArgumentIndex(const std::string &, + bool = true) const; + + size_t numArgs() const { + if (isMethod) { + return arguments.size() - 1; + } + return arguments.size(); + } + + std::optional getRange(uintptr_t addr) { + for (const auto &range : ranges) { + if (addr >= range.start && addr < range.end) { + return range; + } + } + + return std::nullopt; + } + + struct TargetObject { + bool valid = false; + std::string typeName{}; + + virtual ~TargetObject() = default; + + /* Given a register set return the address where the object position + * can be found at the given pc (what about if we don't have this + * location?). + */ + virtual std::optional findAddress(struct user_regs_struct *regs, + uintptr_t pc) const = 0; + }; + + struct Arg final : virtual TargetObject { + struct drgn_object_locator locator; + + ~Arg() final { + drgn_object_locator_deinit(&locator); + } + + std::optional findAddress(struct user_regs_struct *regs, + uintptr_t pc) const final; + }; + + struct Retval final : virtual TargetObject { + ~Retval() final = default; + + std::optional findAddress(struct user_regs_struct *regs, + uintptr_t /* pc */) const final { + return regs->rax; + } + }; +}; + +std::ostream &operator<<(std::ostream &os, const FuncDesc::Range &r); + +class GlobalDesc { + public: + GlobalDesc() = default; + GlobalDesc(std::string name, uintptr_t addr) + : symName{std::move(name)}, baseAddr{addr} {}; + + std::string symName{}; + std::string typeName{}; + uintptr_t baseAddr{0}; +}; diff --git a/src/DrgnUtils.cpp b/src/DrgnUtils.cpp new file mode 100644 index 0000000..0562897 --- /dev/null +++ b/src/DrgnUtils.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "DrgnUtils.h" + +extern "C" { +#include +} + +namespace drgnplusplus { + +void error::Deleter::operator()(drgn_error* err) noexcept { + drgn_error_destroy(err); +} + +const char* error::what() const noexcept { + return ptr->message; +} + +program::program() { + struct drgn_program* prog; + error err(drgn_program_create(NULL, &prog)); + if (err) { + throw err; + } + ptr.reset(prog); +} + +void program::Deleter::operator()(drgn_program* prog) noexcept { + drgn_program_destroy(prog); +} + +func_iterator::func_iterator(drgn_program* prog) { + drgn_func_iterator* ret; + error err(drgn_func_iterator_create(prog, &ret)); + if (err) { + throw err; + } + iter.reset(ret, Deleter()); +} + +func_iterator::func_iterator(program& prog) : func_iterator(prog.get()){}; + +void func_iterator::Deleter::operator()(drgn_func_iterator* _iter) noexcept { + drgn_func_iterator_destroy(_iter); +} + +func_iterator& func_iterator::operator++() { + auto err = drgn_func_iterator_next(iter.get(), ¤t); + if (err) { + throw error(err); + } + if (current == nullptr) { + iter = nullptr; + } + return *this; +} + +} // namespace drgnplusplus diff --git a/src/DrgnUtils.h b/src/DrgnUtils.h new file mode 100644 index 0000000..001253d --- /dev/null +++ b/src/DrgnUtils.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +extern "C" { +// Declare drgn structs and only refer to them by pointers to avoid exposing +// drgn.h. +struct drgn_error; +struct drgn_program; +struct drgn_func_iterator; +struct drgn_qualified_type; +} + +namespace drgnplusplus { + +class error : public std::exception { + public: + struct Deleter { + void operator()(drgn_error* err) noexcept; + }; + error(drgn_error* err) : ptr(err){}; + + operator bool() const { + return static_cast(ptr); + } + + const char* what() const noexcept final; + + private: + std::unique_ptr ptr; +}; + +class program { + public: + struct Deleter { + void operator()(drgn_program* prog) noexcept; + }; + + program(); + program(drgn_program* prog) : ptr(prog){}; + + drgn_program* get() { + return ptr.get(); + } + + private: + std::unique_ptr ptr; +}; + +class func_iterator { + public: + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = drgn_qualified_type; + using pointer = drgn_qualified_type*; + using reference = drgn_qualified_type&; + + struct Deleter { + void operator()(drgn_func_iterator* _iter) noexcept; + }; + + func_iterator(drgn_program* prog); + func_iterator(program& prog); + func_iterator() = default; + func_iterator(drgn_func_iterator* _iter) : iter(_iter, Deleter()) { + } + + reference operator*() const { + return *current; + } + pointer operator->() { + return current; + } + func_iterator& operator++(); + friend bool operator==(const func_iterator& a, const func_iterator& b) { + return a.iter == b.iter; + }; + friend bool operator!=(const func_iterator& a, const func_iterator& b) { + return !(a == b); + }; + + func_iterator begin() { + return ++(*this); + } + func_iterator end() { + return func_iterator(); + } + + private: + std::shared_ptr iter = nullptr; + pointer current = nullptr; +}; + +} // namespace drgnplusplus diff --git a/src/FuncGen.cpp b/src/FuncGen.cpp new file mode 100644 index 0000000..ab1ebbe --- /dev/null +++ b/src/FuncGen.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "FuncGen.h" + +#include +#include + +#include +#include + +#include "ContainerInfo.h" + +#ifndef OSS_ENABLE +#include "cea/object-introspection/internal/FuncGenInternal.h" +#endif + +namespace { + +const std::string typedValueFunc = R"( + void getSizeType(const %1%& t, size_t& returnArg) + { + const uint8_t KindOfPersistentDict = 14; + const uint8_t KindOfDict = 15; + const uint8_t KindOfPersistentVec = 22; + const uint8_t KindOfVec = 23; + const uint8_t KindOfPersistentKeyset = 26; + const uint8_t KindOfKeyset = 27; + const uint8_t KindOfRecord = 29; + const uint8_t KindOfPersistentString = 38; + const uint8_t KindOfString = 39; + const uint8_t KindOfObject = 43; + const uint8_t KindOfResource = 45; + const uint8_t KindOfRFunc = 51; + const uint8_t KindOfRClsMeth = 53; + const uint8_t KindOfClsMeth = 56; + const uint8_t KindOfBoolean = 70; + const uint8_t KindOfInt64 = 74; + const uint8_t KindOfDouble = 76; + const uint8_t KindOfFunc = 82; + const uint8_t KindOfClass = 84; + const uint8_t KindOfLazyClass = 88; + const uint8_t KindOfUninit = 98; + const uint8_t KindOfNull = 100; + + SAVE_DATA((uintptr_t)t.m_type); + switch(t.m_type) { + case KindOfInt64: + case KindOfBoolean: + SAVE_DATA(0); + getSizeType(t.m_data.num, returnArg); + break; + + case KindOfDouble: + SAVE_DATA(1); + getSizeType(t.m_data.dbl, returnArg); + break; + + case KindOfPersistentString: + case KindOfString: + SAVE_DATA(2); + getSizeType(t.m_data.pstr, returnArg); + break; + + case KindOfPersistentDict: + case KindOfDict: + case KindOfPersistentVec: + case KindOfVec: + case KindOfPersistentKeyset: + case KindOfKeyset: + SAVE_DATA(3); + getSizeType(t.m_data.parr, returnArg); + break; + + case KindOfObject: + SAVE_DATA(4); + getSizeType(t.m_data.pobj, returnArg); + break; + + case KindOfResource: + SAVE_DATA(5); + getSizeType(t.m_data.pres, returnArg); + break; + + case KindOfFunc: + SAVE_DATA(8); + getSizeType(t.m_data.pfunc, returnArg); + break; + + case KindOfRFunc: + SAVE_DATA(9); + getSizeType(t.m_data.prfunc, returnArg); + break; + + case KindOfClass: + SAVE_DATA(10); + getSizeType(t.m_data.pclass, returnArg); + break; + + case KindOfClsMeth: + SAVE_DATA(11); + getSizeType(t.m_data.pclsmeth, returnArg); + break; + + case KindOfRClsMeth: + SAVE_DATA(12); + getSizeType(t.m_data.prclsmeth, returnArg); + break; + + case KindOfRecord: + SAVE_DATA(13); + getSizeType(t.m_data.prec, returnArg); + break; + + case KindOfLazyClass: + SAVE_DATA(14); + getSizeType(t.m_data.plazyclass, returnArg); + break; + + case KindOfUninit: + case KindOfNull: + break; + + } + } + )"; + +const std::map defaultTypeToDeclMap = {}; + +const std::map defaultTypeToFuncMap = {}; +} // namespace + +void FuncGen::DeclareGetSize(std::string& testCode, const std::string& type) { + boost::format fmt = + boost::format("void getSizeType(const %1% &t, size_t& returnArg);\n") % + type; + testCode.append(fmt.str()); +} + +void FuncGen::DeclareTopLevelGetSize(std::string& testCode, + const std::string& type) { + boost::format fmt = boost::format("void getSizeType(const %1% &t);\n") % type; + testCode.append(fmt.str()); +} +void FuncGen::DeclareStoreData(std::string& testCode) { + testCode.append("void StoreData(uintptr_t data, size_t& dataSegOffset);\n"); +} +void FuncGen::DeclareAddData(std::string& testCode) { + testCode.append("void AddData(uint64_t data, size_t& dataSegOffset);\n"); +} +void FuncGen::DeclareEncodeData(std::string& testCode) { + testCode.append("size_t EncodeVarint(uint64_t val, uint8_t* buf);\n"); +} +void FuncGen::DeclareEncodeDataSize(std::string& testCode) { + testCode.append("size_t EncodeVarintSize(uint64_t val);\n"); +} +void FuncGen::DefineEncodeData(std::string& testCode) { + std::string func = R"( + size_t EncodeVarint(uint64_t val, uint8_t* buf) { + uint8_t* p = buf; + while (val >= 128) { + *p++ = 0x80 | (val & 0x7f); + val >>= 7; + } + *p++ = uint8_t(val); + return size_t(p - buf); + } + )"; + testCode.append(func); +} +void FuncGen::DefineEncodeDataSize(std::string& testCode) { + std::string func = R"( + size_t EncodeVarintSize(uint64_t val) { + int s = 1; + while (val >= 128) { + ++s; + val >>= 7; + } + return s; + } + )"; + testCode.append(func); +} + +void FuncGen::DefineStoreData(std::string& testCode) { + // TODO: We are encoding twice. Once to check the size and later to + // actually encode. Maybe just do it once leaving a max of uintptr_t + // space at the end. + std::string func = R"( + void StoreData(uint64_t data, size_t& dataSegOffset) { + size_t sz = EncodeVarintSize(data); + if (sz + dataSegOffset < dataSize) { + auto data_base = reinterpret_cast(dataBase); + data_base += dataSegOffset; + size_t data_size = EncodeVarint(data, data_base); + dataSegOffset += data_size; + } else { + dataSegOffset += sz; + } + } + )"; + + testCode.append(func); +} + +void FuncGen::DefineAddData(std::string& testCode) { + std::string func = R"( + void AddData(uint64_t data, size_t& output) { + output += data; + } + )"; + + testCode.append(func); +} + +void FuncGen::DefineTopLevelGetObjectSize(std::string& testCode, + const std::string& rawType, + const std::string& linkageName) { + std::string func = R"( + /* RawType: %1% */ + extern "C" int %2%(const OIInternal::__ROOT_TYPE__* ObjectAddr, size_t* ObjectSize) + { + OIInternal::getSizeType(*ObjectAddr, *ObjectSize); + return 0; + } + )"; + + boost::format fmt = boost::format(func) % rawType % linkageName; + testCode.append(fmt.str()); +} + +void FuncGen::DefineTopLevelGetSizeRef(std::string& testCode, + const std::string& rawType) { + std::string func = R"( + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-attributes" + /* RawType: %1% */ + void __attribute__((used, retain)) getSize_%2$016x(const OIInternal::__ROOT_TYPE__& t) + #pragma GCC diagnostic pop + { + pointers.initialize(); + pointers.add((uintptr_t)&t); + auto data = reinterpret_cast(dataBase); + data[0] = oidMagicId; + data[1] = cookieValue; + data[2] = 0; + + size_t dataSegOffset = 3 * sizeof(uintptr_t); + OIInternal::StoreData((uintptr_t)(&t), dataSegOffset); + JLOG("%1% @"); + JLOGPTR(&t); + OIInternal::getSizeType(t, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + data[2] = dataSegOffset; + dataBase += dataSegOffset; + } + )"; + + boost::format fmt = + boost::format(func) % rawType % std::hash{}(rawType); + testCode.append(fmt.str()); +} + +void FuncGen::DefineTopLevelGetSizePtr(std::string& testCode, + const std::string& type, + const std::string& rawType) { + std::string func = R"( + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-attributes" + /* Type: %1%, RawType: %2% */ + void __attribute__((used, retain)) getSize_%3$016x(const %1% * t) + #pragma GCC diagnostic pop + void getSize(const %1% * t) + { + pointers.initialize(); + auto data = reinterpret_cast(dataBase); + data[0] = oidMagicId; + data[1] = cookieValue; + data[2] = 0; + + size_t dataSegOffset = 3 * sizeof(uintptr_t); + + getSizeType(t, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + data[2] = dataSegOffset; + dataBase += dataSegOffset; + } + )"; + + boost::format fmt = + boost::format(func) % type % rawType % std::hash{}(rawType); + testCode.append(fmt.str()); +} + +void FuncGen::DefineTopLevelGetSizePtrRet(std::string& testCode, + const std::string& rawType) { + std::string func = R"( + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-attributes" + /* Raw Type: %1% */ + size_t __attribute__((used, retain)) getSize(const OIInternal::__ROOT_TYPE__* t) + #pragma GCC diagnostic pop + { + pointers.initialize(); + size_t ret = 0; + pointers.add((uintptr_t)t); + SAVE_DATA((uintptr_t)t); + OIInternal::getSizeType(*t, ret); + return ret; + } + )"; + + boost::format fmt = boost::format(func) % rawType; + testCode.append(fmt.str()); +} + +void FuncGen::DefineTopLevelGetSizeSmartPtr(std::string& testCode, + const std::string& rawType) { + std::string func = R"( + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-attributes" + /* RawType: %1% */ + void __attribute__((used, retain)) getSize_%2$016x(const OIInternal::__ROOT_TYPE__& t) + #pragma GCC diagnostic pop + { + pointers.initialize(); + auto data = reinterpret_cast(dataBase); + data[0] = oidMagicId; + data[1] = cookieValue; + data[2] = 0; + + size_t dataSegOffset = 3 * sizeof(uintptr_t); + OIInternal::StoreData((uintptr_t)(&t), dataSegOffset); + + OIInternal::getSizeType(t, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + OIInternal::StoreData((uintptr_t)123456789, dataSegOffset); + data[2] = dataSegOffset; + dataBase += dataSegOffset; + } + )"; + + boost::format fmt = + boost::format(func) % rawType % std::hash{}(rawType); + testCode.append(fmt.str()); +} + +bool FuncGen::DeclareGetSizeFuncs(std::string& testCode, + const std::set& containerInfo, + bool chaseRawPointers) { + for (auto& cInfo : containerInfo) { + std::string ctype = cInfo.typeName; + ctype = ctype.substr(0, ctype.find("<", 0)); + + if (!typeToFuncMap.contains(cInfo.ctype)) { + LOG(ERROR) << "attempted to use container `" + << containerTypeEnumToStr(cInfo.ctype) + << "` for which a declaration was not provided"; + return false; + } + + auto& func = typeToDeclMap[cInfo.ctype]; + boost::format fmt; + fmt = boost::format(func) % ctype; + /*if (cInfo.ctype == STRING_TYPE) { + fmt = boost::format(func); + } else { + fmt = boost::format(func) % ctype; + }*/ + testCode.append(fmt.str()); + } + + if (chaseRawPointers) { + testCode.append( + "template>>>\n"); + } else { + testCode.append("template\n"); + } + testCode.append("void getSizeType(const T &t, size_t& returnArg);"); + + return true; +} + +bool FuncGen::DefineGetSizeFuncs(std::string& testCode, + const std::set& containerInfo, + bool chaseRawPointers) { + for (auto& cInfo : containerInfo) { + std::string ctype = cInfo.typeName; + ctype = ctype.substr(0, ctype.find("<", 0)); + + if (!typeToFuncMap.contains(cInfo.ctype)) { + LOG(ERROR) << "attempted to use container `" + << containerTypeEnumToStr(cInfo.ctype) + << "` for which a definition was not provided"; + return false; + } + auto& func = typeToFuncMap[cInfo.ctype]; + + boost::format fmt; + fmt = boost::format(func) % ctype; + /*if (cInfo.ctype == STRING_TYPE) { + fmt = boost::format(func); + } else { + fmt = boost::format(func) % ctype; + }*/ + + testCode.append(fmt.str()); + } + + if (chaseRawPointers) { + testCode.append("template\n"); + } else { + testCode.append("template\n"); + } + + testCode.append(R"( + void getSizeType(const T &t, size_t& returnArg) { + JLOG("obj @"); + JLOGPTR(&t); + SAVE_SIZE(sizeof(T)); + } + )"); + + return true; +} + +void FuncGen::DefineGetSizeTypedValueFunc(std::string& testCode, + const std::string& ctype) { + boost::format fmt = boost::format(typedValueFunc) % ctype; + testCode.append(fmt.str()); +} + +void FuncGen::DeclareGetContainer(std::string& testCode) { + std::string func = R"( + template + const typename ContainerAdapter::container_type & get_container (ContainerAdapter &ca) + { + struct unwrap : ContainerAdapter { + static const typename ContainerAdapter::container_type & get (ContainerAdapter &ca) { + return ca.*&unwrap::c; + } + }; + return unwrap::get(ca); + } + )"; + testCode.append(func); +} + +// TODO: remove map initialisation once all container configs are removed from +// the code +FuncGen::FuncGen() + : typeToDeclMap(defaultTypeToDeclMap), typeToFuncMap(defaultTypeToFuncMap) { +#ifndef OSS_ENABLE + typeToDeclMap.insert(typeToDeclMapInternal.begin(), + typeToDeclMapInternal.end()); + typeToFuncMap.insert(typeToFuncMapInternal.begin(), + typeToFuncMapInternal.end()); +#endif +} + +bool FuncGen::RegisterContainer(ContainerTypeEnum ctype, const fs::path& path) { + toml::table container; + try { + container = toml::parse_file(std::string(path)); + } catch (const toml::parse_error& ex) { + LOG(ERROR) << "FuncGen::RegisterContainer: " << path << " : " + << ex.description(); + return false; + } + + toml::table* codegen = container["codegen"].as_table(); + if (!codegen) { + LOG(ERROR) << "a container info file requires an `codegen` table"; + return false; + } + + if (std::optional str = + (*codegen)["decl"].value()) { + typeToDeclMap.emplace(ctype, std::move(*str)); + } else { + LOG(ERROR) << "`codegen.decl` is a required field"; + return false; + } + + if (std::optional str = + (*codegen)["func"].value()) { + typeToFuncMap.emplace(ctype, std::move(*str)); + } else { + LOG(ERROR) << "`codegen.func` is a required field"; + return false; + } + + return true; +} diff --git a/src/FuncGen.h b/src/FuncGen.h new file mode 100644 index 0000000..2c47d2a --- /dev/null +++ b/src/FuncGen.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include + +#include "ContainerInfo.h" + +namespace fs = std::filesystem; + +class FuncGen { + public: + // TODO: remove me once all containers are in toml files + FuncGen(); + bool RegisterContainer(ContainerTypeEnum, const fs::path& path); + + void DeclareStoreData(std::string& testCode); + void DefineStoreData(std::string& testCode); + + void DeclareAddData(std::string& testCode); + void DefineAddData(std::string& testCode); + + void DeclareEncodeData(std::string& testCode); + void DefineEncodeData(std::string& testCode); + + void DeclareEncodeDataSize(std::string& testCode); + void DefineEncodeDataSize(std::string& testCode); + + bool DeclareGetSizeFuncs(std::string& testCode, + const std::set& containerInfo, + bool chaseRawPointers); + bool DefineGetSizeFuncs(std::string& testCode, + const std::set& containerInfo, + bool chaseRawPointers); + + void DeclareGetContainer(std::string& testCode); + + void DeclareGetSize(std::string& testCode, const std::string& type); + + void DeclareTopLevelGetSize(std::string& testCode, const std::string& type); + void DefineTopLevelGetObjectSize(std::string& testCode, + const std::string& type, + const std::string& linkageName); + + void DefineTopLevelGetSizePtr(std::string& testCode, const std::string& type, + const std::string& rawType); + + void DefineTopLevelGetSizeRef(std::string& testCode, + const std::string& rawType); + + void DefineTopLevelGetSizePtrRet(std::string& testCode, + const std::string& type); + + void DefineTopLevelGetSizeSmartPtr(std::string& testCode, + const std::string& rawType); + + void DefineGetSizeTypedValueFunc(std::string& testCode, + const std::string& ctype); + + private: + std::map typeToDeclMap; + std::map typeToFuncMap; +}; diff --git a/src/Metrics.cpp b/src/Metrics.cpp new file mode 100644 index 0000000..900d3da --- /dev/null +++ b/src/Metrics.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "Metrics.h" + +#include + +#include +#include + +/* + * NOTA BENE: Metrics are disabled by default. They are enabled by setting the + * 'OID_METRICS_TRACE' environment variable as detailed in Metrics.h. + * + * This small library to track how much time and other resources we are + * spending on different phases of the execution of OI. + * + * You can instrument some code with: + * ``` + * auto t = Metrics::Tracing("name_of_your_trace"); + * [... some code ...] + * t.stop(); + * ``` + * + * The code above will create a new trace that, when the `.stop()` method + * is called will be appended to the list of traces. + * + * Alternatively, you can use automatically deal with this in every return + * point thanks to C++'s RAII: + * ``` + * Metrics::Tracing unused_var("name_of_your_trace"); + * ``` + * + * When you want to collect the data, `::showTraces()` to print the data to + * stdout, and `::saveTraces(file)` to save it to disk using JSON. + */ +namespace ObjectIntrospection::Metrics { + +static inline TraceFlags parseTraceFlags(const char* flags) { + if (flags == nullptr) { + return TraceFlags{}; + } + return { + .time = strcasestr(flags, "time") != nullptr, + .rss = strcasestr(flags, "rss") != nullptr, + }; +} + +Tracing::Static Tracing::static_{}; + +Tracing::Static::Static() { + traceEnabled = parseTraceFlags(std::getenv(traceEnvKey)); + + errno = 0; + if (auto pageSizeBytes = sysconf(_SC_PAGESIZE); pageSizeBytes > 0) { + pageSizeKB = pageSizeBytes / 1024; + } else { + std::perror("Failed to retrieve page size"); + } +} + +Tracing::Static::~Static() { + if (!traceEnabled) { + return; + } + + Tracing::saveTraces(Tracing::outputPath()); + traces.clear(); +} + +uint32_t Tracing::getNextIndex() { + // NOTE: we already own the lock on static_.traces + return static_cast(static_.traces.size()); +} + +Tracing::TimePoint Tracing::fetchTime() { + if (!static_.traceEnabled.time) { + return TimePoint{}; + } + + return std::chrono::high_resolution_clock::now(); +} + +long Tracing::fetchRssUsage() { + if (!static_.traceEnabled.rss) { + return 0; + } + + std::ifstream statStream("/proc/self/stat"); + + // Placeholders as we don't care about these at the minute. There are more + // fields in /stat that we don't have here + /* std::string pid, comm, state, ppid, pgrp, session, tty_nr, tpgid, flags, + minflt, cminflt, majflt, cmajflt, utime, stime, cutime, cstime, + priority, nice, num_threads, itrealvalue, starttime, vsize; + */ + for (size_t i = 0; i < 23; ++i) { + statStream.ignore(std::numeric_limits::max(), ' '); + } + + // We care about this field + long rss = 0; + statStream >> rss; + + return rss * static_.pageSizeKB; +} + +void Tracing::stop() { + ended = true; + if (!static_.traceEnabled) { + return; + } + + using namespace std::chrono; + auto stopTs = fetchTime(); + auto duration = duration_cast(stopTs - startTs); + auto rssAfterBytes = fetchRssUsage(); + + std::lock_guard guard{static_.mutex}; + // Can't use emplace_back() because of old clang++ on CI + static_.traces.push_back({getNextIndex(), std::move(traceName), + duration.count(), rssBeforeBytes, rssAfterBytes}); +} + +void Tracing::saveTraces(const std::filesystem::path& output) { + std::ofstream osf{output}; + if (!osf) { + perror("Failed to open output file"); + return; + } + + osf << "["; + for (const auto& span : static_.traces) { + if (span.index > 0) { + osf << ","; + } + + osf << "{\"name\":\"" << span.name << "\""; + osf << ",\"index\":" << span.index; + + if (static_.traceEnabled.time) { + osf << ",\"duration_ns\":" << span.duration; + } + + if (static_.traceEnabled.rss) { + osf << ",\"rss_before_bytes\":" << span.rssBeforeBytes; + osf << ",\"rss_after_bytes\":" << span.rssAfterBytes; + } + + osf << "}"; + } + osf << "]\n"; +} + +const char* Tracing::outputPath() { + const char* output = std::getenv(outputEnvKey); + + if (output == nullptr) { + output = "oid_metrics.json"; + } + return output; +} + +std::ostream& operator<<(std::ostream& out, const TraceFlags& tf) { + if (tf.time && tf.rss) { + out << "time, rss"; + } else if (tf.time) { + out << "time"; + } else if (tf.rss) { + out << "rss"; + } else { + out << "disabled"; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const Span& span) { + out << "Span for: " << span.name << " (" << span.index << ")\n"; + out << " Duration: " << span.duration << " ns\n"; + out << " RSS before: " << span.rssBeforeBytes << " bytes\n"; + out << " RSS after: " << span.rssAfterBytes << " bytes\n"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const std::vector& spans) { + out << "Showing all spans:\n"; + out << "==================\n\n"; + + for (const auto& span : spans) { + out << span; + } + return out; +} + +} // namespace ObjectIntrospection::Metrics diff --git a/src/Metrics.h b/src/Metrics.h new file mode 100644 index 0000000..b09c101 --- /dev/null +++ b/src/Metrics.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ObjectIntrospection::Metrics { + +constexpr auto traceEnvKey = "OID_METRICS_TRACE"; +constexpr auto outputEnvKey = "OID_METRICS_OUTPUT"; + +/* + * Control which metric are collected using the environment variable + * OID_METRICS_TRACE. It accepts a comma-separated list of metrics: time, rss. + * By default, no metrics are collected. + * The metrics are written to the path specified by the environment variable + * OID_METRICS_OUTPUT. If not specified, they are written into + * "oid_metrics.json". + */ +struct TraceFlags { + bool time : 1 = false; + bool rss : 1 = false; + + operator bool() const { + return time || rss; + } +}; + +struct Span { + uint32_t index; + std::string name; + int64_t duration; + long rssBeforeBytes; + long rssAfterBytes; +}; + +class Tracing final { + private: + /* + * Independent static variables might be destroyed before our std::atexit() + * handler is called, leading to an use-after-free error. Instead, we group + * all static variables in the following structure and put our atexit() + * handler's code in its destructor. This ensure that all variables are + * destroyed **AFTER** the call to ~Static(). + */ + static struct Static { + long pageSizeKB; + TraceFlags traceEnabled; + std::vector traces; + std::mutex mutex; + + Static(); + ~Static(); + } static_; + + using TimePoint = std::chrono::high_resolution_clock::time_point; + + public: + /* + * Metrics::Tracing("bad"); + * + * Usage is Metrics::Tracing __varname_(...) The code above is an improper + * use of the Tracing library. The tracing object above is not stored in a + * variable. So it is immediately destroyed and won't record the expected + * results. [[nodiscard]] flags the code above with a warning, which we + * enforce as an error in our cmake/CompilerWarnings.cmake. + */ + [[nodiscard]] explicit Tracing(const char* name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = name; + } + + [[nodiscard]] explicit Tracing(const std::string& name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = name; + } + + [[nodiscard]] explicit Tracing(std::string&& name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = std::move(name); + } + + Tracing() = delete; + Tracing(const Tracing& other) : Tracing{other.traceName} { + } + Tracing(Tracing&&) noexcept = default; + + Tracing& operator=(Tracing&) = delete; + Tracing& operator=(Tracing&&) = delete; + + ~Tracing() { + if (!ended) { + stop(); + } + } + + void reset() { + if (!Tracing::isEnabled()) { + return; + } + startTs = fetchTime(); + rssBeforeBytes = fetchRssUsage(); + } + + void rename(const char* name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = name; + } + + void rename(const std::string& name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = name; + } + + void rename(std::string&& name) { + if (!Tracing::isEnabled()) { + return; + } + traceName = std::move(name); + } + + void stop(); + + static TraceFlags& isEnabled() { + return static_.traceEnabled; + } + static const char* outputPath(); + static void saveTraces(const std::filesystem::path&); + + private: + static uint32_t getNextIndex(); + static TimePoint fetchTime(); + static long fetchRssUsage(); + + bool ended{false}; + std::string traceName{}; + TimePoint startTs{Tracing::fetchTime()}; + long rssBeforeBytes{Tracing::fetchRssUsage()}; +}; + +std::ostream& operator<<(std::ostream&, const TraceFlags&); +std::ostream& operator<<(std::ostream&, const Span&); +std::ostream& operator<<(std::ostream&, const std::vector&); + +} // namespace ObjectIntrospection::Metrics diff --git a/src/OICache.cpp b/src/OICache.cpp new file mode 100644 index 0000000..0fffa3b --- /dev/null +++ b/src/OICache.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OICache.h" + +#include + +#include +#include +#include + +#include "Descs.h" +#include "OICodeGen.h" +#include "Serialize.h" + +#ifndef OSS_ENABLE +#include "cea/object-introspection/internal/ManifoldCache.h" +#endif + +static std::optional> getEntName( + SymbolService &symbols, const irequest &req, OICache::Entity ent) { + if (ent == OICache::Entity::FuncDescs || + ent == OICache::Entity::GlobalDescs) { + return req.func; + } else { + if (req.type == "global") { + const auto &globalDesc = symbols.findGlobalDesc(req.func); + if (!globalDesc) { + return std::nullopt; + } + + return globalDesc->typeName; + } else { + const auto &funcDesc = symbols.findFuncDesc(req); + if (!funcDesc) { + return std::nullopt; + } + + const auto &arg = funcDesc->getArgument(req.arg); + if (!arg) { + return std::nullopt; + } + + return arg->typeName; + } + } +} + +std::optional OICache::getPath(const irequest &req, + Entity ent) const { + auto hash = [](const std::string &str) { + return std::to_string(std::hash{}(str)); + }; + + auto ext = extensions[static_cast(ent)]; + + const auto &entName = getEntName(*symbols, req, ent); + if (!entName.has_value()) { + return std::nullopt; + } + + return basePath / (hash(*entName) + ext); +} + +template +bool OICache::load(const irequest &req, Entity ent, T &data) { + if (!isEnabled()) + return false; + try { + auto buildID = symbols->locateBuildID(); + if (!buildID) { + LOG(ERROR) << "Failed to locate buildID"; + return false; + } + + auto cachePath = getPath(req, ent); + if (!cachePath.has_value()) { + LOG(ERROR) << "Failed to get cache path for " << req.type << ':' + << req.func << ':' << req.arg << '/' + << static_cast(ent); + return false; + } + + LOG(INFO) << "Loading cache " << *cachePath; + std::ifstream ifs(*cachePath); + boost::archive::text_iarchive ia(ifs); + + std::string cacheBuildId; + ia >> cacheBuildId; + if (cacheBuildId != *buildID) { + LOG(ERROR) << "The cache's build id '" << cacheBuildId + << "' doesn't match the target's build id '" << *buildID + << "'"; + return false; + } + + ia >> data; + return true; + } catch (const std::exception &e) { + LOG(WARNING) << "Failed to load from cache: " << e.what(); + return false; + } +} + +template +bool OICache::store(const irequest &req, Entity ent, const T &data) { + if (!isEnabled()) + return false; + try { + auto buildID = symbols->locateBuildID(); + if (!buildID) { + LOG(ERROR) << "Failed to locate buildID"; + return false; + } + + auto cachePath = getPath(req, ent); + if (!cachePath.has_value()) { + LOG(ERROR) << "Failed to get cache path for " << req.type << ':' + << req.func << ':' << req.arg << '/' + << static_cast(ent); + return false; + } + + LOG(INFO) << "Storing cache " << *cachePath; + std::ofstream ofs(*cachePath); + boost::archive::text_oarchive oa(ofs); + + oa << *buildID; + oa << data; + return true; + } catch (const std::exception &e) { + LOG(WARNING) << "Failed to write to cache: " << e.what(); + return false; + } +} + +#define INSTANTIATE_ARCHIVE(...) \ + template bool OICache::load(const irequest &, Entity, __VA_ARGS__ &); \ + template bool OICache::store(const irequest &, Entity, const __VA_ARGS__ &); + +INSTANTIATE_ARCHIVE(std::pair) +INSTANTIATE_ARCHIVE(std::unordered_map>) +INSTANTIATE_ARCHIVE( + std::unordered_map>) +INSTANTIATE_ARCHIVE(std::map) + +#undef INSTANTIATE_ARCHIVE + +// Upload all contents of cache for this request +bool OICache::upload([[maybe_unused]] const irequest &req) { +#ifndef OSS_ENABLE + if (!isEnabled() || downloadedRemote || !enableUpload) + return true; + std::vector files; + + for (size_t i = 0; i < static_cast(OICache::Entity::MAX); i++) { + auto cachePath = getPath(req, static_cast(i)); + if (!cachePath.has_value()) { + LOG(ERROR) << "Failed to get cache path for " << req.type << ':' + << req.func << ':' << req.arg << '/' << static_cast(i); + return false; + } + files.push_back(*cachePath); + } + + auto hash = generateRemoteHash(req); + if (hash.empty()) { + LOG(ERROR) << "failed to generate remote lookup hash"; + return false; + } + + return ObjectIntrospection::ManifoldCache::upload(hash, files); +#else + if (isEnabled() && !downloadedRemote && enableUpload) { + // We tried to download when support is not enabled! + LOG(ERROR) << "Tried to upload artifacts when support is not enabled!"; + return false; + } + return true; +#endif +} + +// Try to fetch contents of cache +bool OICache::download([[maybe_unused]] const irequest &req) { +#ifndef OSS_ENABLE + if (!isEnabled() || !enableDownload) + return true; + + auto hash = generateRemoteHash(req); + if (hash.empty()) { + LOG(ERROR) << "failed to generate remote lookup hash"; + return false; + } + + if (basePath.filename() != hash) { + // Use a subdirectory per hash shard to avoid conflicts + basePath /= hash; + if (fs::exists(basePath.parent_path()) && !fs::exists(basePath)) + fs::create_directory(basePath); + } + if (ObjectIntrospection::ManifoldCache::download(hash, basePath)) { + downloadedRemote = true; + return true; + } + if (abortOnLoadFail) { + LOG(ERROR) << "We weren't able to pull artifacts when requested, " + "aborting run!"; + // If we aren't uploading, quit early as we requested a download and + // weren't able to get it. + return false; + } + return true; +#else + if (isEnabled() && enableDownload) { + // We tried to download when support is not enabled! + LOG(ERROR) << "Tried to download artifacts when support is not enabled!"; + return false; + } + return true; +#endif +} + +std::string OICache::generateRemoteHash(const irequest &req) { + auto buildID = symbols->locateBuildID(); + if (!buildID) { + LOG(ERROR) << "Failed to locate buildID"; + return ""; + } + + std::string remote_cache_id = *buildID + "/" + req.func + "/" + req.arg + + "/" + generatorConfig.toString(); + + LOG(INFO) << "generating remote hash from: " << remote_cache_id; + return std::to_string(std::hash{}(remote_cache_id)); +} diff --git a/src/OICache.h b/src/OICache.h new file mode 100644 index 0000000..2fc50a3 --- /dev/null +++ b/src/OICache.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include + +#include "OICodeGen.h" +#include "OIParser.h" +#include "SymbolService.h" + +namespace fs = std::filesystem; + +class OICache { + public: + fs::path basePath{}; + std::shared_ptr symbols{}; + bool downloadedRemote = false; + bool enableUpload = false; + bool enableDownload = false; + bool abortOnLoadFail = false; + + // We need the generator config to download the cache + // with the matching configuration. + OICodeGen::Config generatorConfig{}; + + // Entity is used to index the `extensions` array + // So we must keep the Entity enum and `extensions` array in sync! + enum class Entity { + Source, + Object, + FuncDescs, + GlobalDescs, + TypeHierarchy, + PaddingInfo, + MAX + }; + static constexpr std::array(Entity::MAX)> + extensions{".cc", ".o", ".fd", ".gd", ".th", ".pd"}; + + bool isEnabled() const { + return !basePath.empty(); + } + std::optional getPath(const irequest &, Entity) const; + template + bool store(const irequest &, Entity, const T &); + template + bool load(const irequest &, Entity, T &); + + bool upload(const irequest &req); + bool download(const irequest &req); + + private: + std::string generateRemoteHash(const irequest &); +}; diff --git a/src/OICodeGen.cpp b/src/OICodeGen.cpp new file mode 100644 index 0000000..8bab4ff --- /dev/null +++ b/src/OICodeGen.cpp @@ -0,0 +1,3728 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OICodeGen.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FuncGen.h" +#include "OIParser.h" +#include "PaddingHunter.h" +#include "SymbolService.h" +#ifndef OSS_ENABLE +#include "cea/object-introspection/internal/FuncGenInternal.h" +#endif + +namespace fs = std::filesystem; + +static size_t g_level = 0; + +#undef VLOG +#define VLOG(verboselevel) \ + LOG_IF(INFO, VLOG_IS_ON(verboselevel)) << std::string(2 * g_level, ' ') + +// typeName, numTemplateParams, ctype, header, namespaces... +// formatting disabled due to line length becoming a mess +// clang-format off +static const std::vector defaultContainerInfoList = { +}; +// clang-format on + +std::unique_ptr OICodeGen::buildFromConfig(const Config &c) { + auto cg = std::unique_ptr(new OICodeGen(c)); + + for (const auto &path : c.containerConfigPaths) { + if (!cg->registerContainer(path)) { + LOG(ERROR) << "failed to register container: " << path; + return nullptr; + } + } + + return cg; +} + +// TODO: remove containerInfoList initialisation once all container configs are +// removed from the code +OICodeGen::OICodeGen(const Config &c) : config{c} { +#ifndef OSS_ENABLE + containerInfoList.reserve(containerInfoListInternal.size() + + defaultContainerInfoList.size()); + for (const auto &el : containerInfoListInternal) { + containerInfoList.push_back(std::make_unique(el)); + } +#endif + + containerInfoList.reserve(defaultContainerInfoList.size()); + for (const auto &el : defaultContainerInfoList) { + containerInfoList.push_back(std::make_unique(el)); + } + + // TODO: Should folly::Range just be added as a container? + auto typesToStub = std::array{ + "SharedMutex", + "EnumMap", + "function", + "Function", + "ConcurrentHashMap", + "DelayedDestruction", + "McServerSession", + "Range", + "ReadResumableHandle", + "tuple", + "CountedIntrusiveList", + "EventBaseAtomicNotificationQueue", + /* Temporary IOBuf ring used for scattered read/write. + * It's only used for communication and should be empty the rest of the + * time. So we shouldn't loose too much visibility by stubbing it out. + */ + "IOBufIovecBuilder", + /* struct event from libevent + * Its linked lists are not always initialised, leading to SegV in our JIT + * code. We can't stub the linked list themselves, as they're anonymous + * structs. + */ + "event", + }; + + config.membersToStub.reserve(typesToStub.size()); + for (auto &type : typesToStub) { + config.membersToStub.emplace_back(type, "*"); + } + + // `knownTypes` has been made obsolete by the introduction of using the + // `OIInternal` namespace. Instead of deleting all of the related code + // however, for now we just define the `knownTypes` list to be empty, as this + // will make it easier to selectively revert our changes if it turns out that + // there are issues with the new approach. + knownTypes = {"IPAddress"}; + + sizeMap["SharedMutex"] = sizeof(folly::SharedMutex); +} + +bool OICodeGen::registerContainer(const fs::path &path) { + VLOG(1) << "registering container, path: " << path; + auto info = ContainerInfo::loadFromFile(path); + if (!info) { + return false; + } + + if (!funcGen.RegisterContainer(info->ctype, path)) { + return false; + } + + VLOG(1) << "registered container, type: " << info->typeName; + containerInfoList.emplace_back(std::move(info)); + return true; +} + +bool OICodeGen::isKnownType(const std::string &type) { + std::string matched; + return isKnownType(type, matched); +} + +bool OICodeGen::isKnownType(const std::string &type, std::string &matched) { + if (auto it = std::ranges::find(knownTypes, type); it != knownTypes.end()) { + matched = *it; + return true; + } + + if (type.rfind("allocator<", 0) == 0 || + type.rfind("new_allocator<", 0) == 0) { + matched = type; + return true; + } + + if (auto opt = isTypeToStub(type)) { + matched = opt.value(); + return true; + } + + return false; +} + +std::optional OICodeGen::fullyQualifiedName( + struct drgn_type *type) { + auto entry = fullyQualifiedNames.find(type); + if (entry != fullyQualifiedNames.end()) { + return entry->second.contents; + } + + char *name = nullptr; + size_t length = 0; + auto *err = drgn_type_fully_qualified_name(type, &name, &length); + if (err != nullptr || name == nullptr) { + return std::nullopt; + } + + auto typeNamePair = + fullyQualifiedNames.emplace(type, DrgnString{name, length}).first; + return typeNamePair->second.contents; +} + +std::optional OICodeGen::getContainerInfo( + struct drgn_type *type) { + auto name = fullyQualifiedName(type); + if (!name.has_value()) { + return std::nullopt; + } + + for (auto it = containerInfoList.rbegin(); it != containerInfoList.rend(); + it++) { + const auto &info = *it; + if (name->starts_with(info->typeName)) { + // Blob must match exactly. Otherwise it also matches BlobsMap + if (info->ctype == CAFFE2_BLOB_TYPE && *name != "caffe2::Blob") { + return std::nullopt; + } + + // IOBuf must also match exactly, or it also matches IOBufQueue + if (info->ctype == FOLLY_IOBUF_TYPE && *name != "folly::IOBuf") { + continue; + } + + return *info; + } + } + return std::nullopt; +} + +bool OICodeGen::isContainer(struct drgn_type *type) { + return getContainerInfo(type).has_value(); +} + +std::string OICodeGen::preProcessUniquePtr(struct drgn_type *type, + std::string name) { + std::string typeName; + std::string deleterName; + + size_t end = name.rfind('>') - 1; + size_t pos = end; + size_t begin = 0; + bool found = false; + + int unmatchedTemplate = 0; + while (pos > 0) { + if (name[pos] == '>') { + unmatchedTemplate++; + } + if (name[pos] == '<') { + unmatchedTemplate--; + } + if (unmatchedTemplate == 0 && name[pos] == ',') { + begin = pos + 2; + deleterName = name.substr(begin, end - begin + 1); + + size_t offset = std::string("unique_ptr<").size(); + typeName = name.substr(offset, pos - offset); + found = true; + + break; + } + + pos--; + } + + VLOG(2) << "Deleter name: " << deleterName << " typeName: " << typeName; + + if (deleterName.find("default_delete") == std::string::npos && found) { + size_t typeSize = drgn_type_size(type); + + constexpr size_t defaultDeleterSize = sizeof(std::unique_ptr); + constexpr size_t cFunctionDeleterSize = + sizeof(std::unique_ptr); + constexpr size_t stdFunctionDeleterSize = + sizeof(std::unique_ptr>); + + if (typeSize == defaultDeleterSize) { + name.replace(begin, end - begin + 1, "default_delete<" + typeName + ">"); + } else if (typeSize == cFunctionDeleterSize) { + name.replace(begin, end - begin + 1, "void(*)(" + typeName + "*)"); + } else if (typeSize == stdFunctionDeleterSize) { + name.replace(begin, end - begin + 1, + "std::function"); + } else { + LOG(ERROR) << "Unhandled case, unique_ptr size: " << typeSize; + } + } + + return name; +} + +void OICodeGen::prependQualifiers(enum drgn_qualifiers qualifiers, + std::string &sb) { + // const qualifier is the only one we're interested in + if ((qualifiers & DRGN_QUALIFIER_CONST) != 0) { + sb += "const "; + } +} + +void OICodeGen::removeTemplateParamAtIndex(std::vector ¶ms, + const size_t index) { + if (index < params.size()) { + params.erase(params.begin() + index); + } +} + +std::string OICodeGen::stripFullyQualifiedName( + const std::string &fullyQualifiedName) { + std::vector lines; + boost::iter_split(lines, fullyQualifiedName, boost::first_finder("::")); + return lines[lines.size() - 1]; +} + +std::string OICodeGen::stripFullyQualifiedNameWithSeparators( + const std::string &fullyQualifiedName) { + std::vector stack; + std::string sep = " ,<>()"; + std::string tmp; + + for (auto &c : fullyQualifiedName) { + if (sep.find(c) == std::string::npos) { + stack.push_back(std::string(1, c)); + } else { + tmp = ""; + while (!stack.empty() && + sep.find(stack[stack.size() - 1]) == std::string::npos) { + tmp = stack[stack.size() - 1] + tmp; + stack.pop_back(); + } + tmp = stripFullyQualifiedName(tmp); + + stack.push_back(tmp); + + if (c == '>' || c == ')') { + std::string findStr = (c == '>') ? "<" : "("; + tmp = ""; + while (!stack.empty() && stack[stack.size() - 1] != findStr) { + tmp = stack[stack.size() - 1] + tmp; + stack.pop_back(); + } + if (stack.empty()) + return ""; + + // Pop the '<' or '(' + stack.pop_back(); + if (c == '>') { + stack.push_back("<" + tmp + ">"); + } else { + stack.push_back("(" + tmp + ")"); + } + } else { + stack.push_back(std::string(1, c)); + } + } + } + + tmp = ""; + for (auto &e : stack) { + tmp += e; + } + + return tmp; +} + +// Replace a specific template parameter with a generic DummySizedOperator +void OICodeGen::replaceTemplateOperator( + TemplateParamList &template_params, + std::vector &template_params_strings, size_t index) { + if (index >= template_params.size()) { + // Should this happen? + return; + } + + size_t comparatorSz = 1; + struct drgn_type *cmpType = template_params[index].first.type; + if (isDrgnSizeComplete(cmpType)) { + comparatorSz = drgn_type_size(cmpType); + } else { + // Don't think there is anyway to accurately get data from the container + } + + // Alignment is input to alignas. alignas(0) is supposed to be ignored. + // So we can safely specify that if we can't query the alignment requirement. + size_t alignment = 0; + if (drgn_type_has_members(cmpType) && drgn_type_num_members(cmpType) == 0) { + comparatorSz = 0; + } else { + auto alignmentOpt = getAlignmentRequirements(cmpType); + if (!alignmentOpt.has_value()) { + // Not sure when/if this would happen. Just log for now. + LOG(ERROR) << "Failed to get alignment for " << cmpType; + } else { + alignment = *alignmentOpt / CHAR_BIT; + } + } + + template_params_strings[index] = "DummySizedOperator<" + + std::to_string(comparatorSz) + "," + + std::to_string(alignment) + ">"; +} + +void OICodeGen::replaceTemplateParameters( + struct drgn_type *type, TemplateParamList &template_params, + std::vector &template_params_strings, + const std::string &nameWithoutTemplate) { + auto containerInfo = getContainerInfo(type); + if (!containerInfo.has_value()) { + LOG(ERROR) << "Unknown container type: " << nameWithoutTemplate; + return; + } + + // Some containers will need special handling + if (nameWithoutTemplate == "bimap<") { + // TODO: The end parameters seem to be wild cards to pass in things like + // allocator or passing complex relationships between the keys. + removeTemplateParamAtIndex(template_params_strings, 4); + removeTemplateParamAtIndex(template_params_strings, 3); + removeTemplateParamAtIndex(template_params_strings, 2); + } else { + for (auto const &index : containerInfo->replaceTemplateParamIndex) { + replaceTemplateOperator(template_params, template_params_strings, index); + } + if (containerInfo->allocatorIndex) { + removeTemplateParamAtIndex(template_params_strings, + *containerInfo->allocatorIndex); + } + } +} + +bool OICodeGen::buildName(struct drgn_type *type, std::string &text, + std::string &outName) { + int ptrDepth = 0; + struct drgn_type *ut = type; + while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) { + ut = drgn_type_type(ut).type; + ptrDepth++; + } + + size_t pos = text.find('<'); + if (pos == std::string::npos) { + outName = text; + return false; + } + + std::string nameWithoutTemplate = text.substr(0, pos + 1); + + std::string tmpName; + if (!buildNameInt(ut, nameWithoutTemplate, tmpName)) { + return false; + } + outName = tmpName + std::string(ptrDepth, '*'); + return true; +} + +bool OICodeGen::buildNameInt(struct drgn_type *type, + std::string &nameWithoutTemplate, + std::string &outName) { + // Calling buildName only makes sense if a type is a container and has + // template parameters. For a generic template class, we just flatten the + // name into anything reasonable (e.g. Foo becomes Foo_int_). + if (!isContainer(type) || !drgn_type_has_template_parameters(type)) { + return false; + } + size_t numTemplateParams = drgn_type_num_template_parameters(type); + if (numTemplateParams == 0) { + return false; + } + + TemplateParamList templateParams; + if (!getTemplateParams(type, numTemplateParams, templateParams)) { + LOG(ERROR) << "Failed to get template params"; + return false; + } + + if (templateParams.size() == 0) { + LOG(ERROR) << "Template parameters missing"; + } + + std::vector templateParamsStrings; + + for (size_t i = 0; i < templateParams.size(); i++) { + auto &[p, value] = templateParams[i]; + enum drgn_qualifiers qualifiers = p.qualifiers; + prependQualifiers(qualifiers, outName); + + std::string templateParamName; + + if (!value.empty()) { + if (drgn_type_kind(p.type) == DRGN_TYPE_ENUM) { + /* + * We must reference scoped enums by fully-qualified name to keep the + * C++ compiler happy. + * + * i.e. we want to end up with: + * isset_bitset<1, + * apache::thrift::detail::IssetBitsetOption::Unpacked> + * + * If we instead just used the enum's value, we'd instead have: + * isset_bitset<1, 0> + * However, this implicit conversion from an integer is not valid for + * C++ scoped enums. + * + * Unscoped enums (C-style enums) do not need this special treatment as + * they are implicitly convertible to their integral values. + * Conveniently for us, drgn returns them to us as DRGN_TYPE_INT so they + * are not processed here. + */ + // TODO Better handling of this enumVal? + // We converted from a numeric value into a string earlier and now back + // into a numeric value here. + // Does this indexing work correctly for signed-integer-valued enums? + // + // This code only applies to containers with an enum template parameter, + // of which we only currently have one, so I think these problems don't + // matter for now. + + auto enumVal = std::stoull(value); + struct drgn_type_enumerator *enumerators = + drgn_type_enumerators(p.type); + templateParamName = *fullyQualifiedName(p.type); + templateParamName += "::"; + templateParamName += enumerators[enumVal].name; + } else { + // We can just directly use the value for other literals + templateParamName = value; + } + } else if (auto it = typeToNameMap.find(p.type); + it != typeToNameMap.end()) { + // Use the type's allocated name if it exists + templateParamName = it->second; + } else if (drgn_type_has_tag(p.type)) { + const char *templateParamNameChr = drgn_type_tag(p.type); + if (templateParamNameChr != nullptr) { + templateParamName = std::string(templateParamNameChr); + } else { + return false; + } + } else if (drgn_type_has_name(p.type)) { + const char *templateParamNameChr = drgn_type_name(p.type); + if (templateParamNameChr != nullptr) { + templateParamName = std::string(templateParamNameChr); + } else { + return false; + } + } else if (drgn_type_kind(p.type) == DRGN_TYPE_FUNCTION) { + char *defStr = nullptr; + if (drgn_format_type_name(p, &defStr)) { + LOG(ERROR) << "Failed to get formatted string for " << p.type; + templateParamName = std::string(""); + } else { + templateParamName = defStr; + free(defStr); + } + } else if (drgn_type_kind(p.type) == DRGN_TYPE_POINTER) { + char *defStr = nullptr; + + if (drgn_format_type_name(p, &defStr)) { + LOG(ERROR) << "Failed to get formatted string for " << p.type; + templateParamName = std::string(""); + } else { + drgn_qualified_type underlyingType = drgn_type_type(p.type); + templateParamName = defStr; + free(defStr); + + if (drgn_type_kind(underlyingType.type) == DRGN_TYPE_FUNCTION) { + size_t index = templateParamName.find(" "); + + if (index != std::string::npos) { + templateParamName = stripFullyQualifiedNameWithSeparators( + templateParamName.substr(0, index + 1)) + + "(*)" + + stripFullyQualifiedNameWithSeparators( + templateParamName.substr(index + 1)); + } + } + } + } else if (drgn_type_kind(p.type) == DRGN_TYPE_VOID) { + templateParamName = std::string("void"); + } else if (drgn_type_kind(p.type) == DRGN_TYPE_ARRAY) { + size_t elems = 1; + struct drgn_type *arrayElementType = nullptr; + getDrgnArrayElementType(p.type, &arrayElementType, elems); + + if (drgn_type_has_name(arrayElementType)) { + templateParamName = drgn_type_name(arrayElementType); + } else { + LOG(ERROR) << "Failed4 to get typename "; + return false; + } + } else { + LOG(ERROR) << "Failed3 to get typename "; + return false; + } + + VLOG(3) << "Template parameter " << templateParamName; + // templateParamName = templateTransformType(templateParamName); + templateParamName = + stripFullyQualifiedNameWithSeparators(templateParamName); + std::string recursiveParam; + if (p.type != nullptr && + buildName(p.type, templateParamName, recursiveParam)) { + templateParamName = recursiveParam; + } + + VLOG(3) << "Template parameter after templateTransformType " + << templateParamName; + + templateParamsStrings.push_back(templateParamName); + } + + replaceTemplateParameters(type, templateParams, templateParamsStrings, + nameWithoutTemplate); + + outName = nameWithoutTemplate; + for (size_t i = 0; i < templateParamsStrings.size(); i++) { + auto &[p, value] = templateParams[i]; + enum drgn_qualifiers qualifiers = p.qualifiers; + prependQualifiers(qualifiers, outName); + + outName += templateParamsStrings[i]; + if (i != templateParamsStrings.size() - 1) { + outName += ", "; + } else { + outName += " >"; + } + } + return true; +} + +bool OICodeGen::getTemplateParams( + struct drgn_type *type, size_t numTemplateParams, + std::vector> &v) { + struct drgn_type_template_parameter *tParams = + drgn_type_template_parameters(type); + + for (size_t i = 0; i < numTemplateParams; i++) { + const struct drgn_object *obj = nullptr; + struct drgn_error *err = drgn_template_parameter_object(&tParams[i], &obj); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up template parameter " << err->code + << " " << err->message; + return false; + } + + std::string value; + struct drgn_qualified_type t {}; + + if (obj == nullptr) { + err = drgn_template_parameter_type(&tParams[i], &t); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up template parameter " << err->code + << " " << err->message; + return false; + } + } else { + t.type = obj->type; + t.qualifiers = obj->qualifiers; + if (obj->encoding == DRGN_OBJECT_ENCODING_BUFFER) { + uint64_t size = drgn_object_size(obj); + char *buf = nullptr; + if (size <= sizeof(obj->value.ibuf)) { + buf = (char *)&(obj->value.ibuf); + } else { + buf = obj->value.bufp; + } + + if (buf != nullptr) { + value = std::string(buf); + } + } else if (obj->encoding == DRGN_OBJECT_ENCODING_SIGNED) { + value = std::to_string(obj->value.svalue); + } else if (obj->encoding == DRGN_OBJECT_ENCODING_UNSIGNED) { + value = std::to_string(obj->value.uvalue); + } else if (obj->encoding == DRGN_OBJECT_ENCODING_FLOAT) { + value = std::to_string(obj->value.fvalue); + } else { + LOG(ERROR) << "Unsupported OBJ encoding for getting template parameter"; + return false; + } + } + + v.push_back({t, value}); + } + return true; +} + +std::string OICodeGen::transformTypeName(struct drgn_type *type, + std::string &text) { + VLOG(3) << "Original String " << text; + + std::string tmp = stripFullyQualifiedNameWithSeparators(text); + + boost::regex re{""}; + std::string fmt{""}; + + std::string output = boost::regex_replace(tmp, re, fmt); + + VLOG(3) << "New Output string is " << output; + + std::string buildNameOutput; + if (!buildName(type, text, buildNameOutput)) { + buildNameOutput = output; + } + VLOG(3) << "BuildName Output string is " << buildNameOutput; + + if (buildNameOutput.starts_with("unique_ptr")) { + buildNameOutput = preProcessUniquePtr(type, buildNameOutput); + VLOG(3) << "unique_ptr preprocessed: " << buildNameOutput; + } + + return buildNameOutput; +} + +bool OICodeGen::getContainerTemplateParams(struct drgn_type *type, + bool &ifStub) { + if (containerTypeMapDrgn.find(type) != containerTypeMapDrgn.end()) { + return true; + } + + std::string typeName; + + if (drgn_type_has_tag(type)) { + typeName = drgn_type_tag(type); + } else if (drgn_type_has_name(type)) { + typeName = drgn_type_name(type); + } else { + LOG(ERROR) << "Failed1 to get typename: kind " << drgnKindStr(type); + return false; + } + typeName = transformTypeName(type, typeName); + + auto containerInfo = getContainerInfo(type); + if (!containerInfo.has_value()) { + LOG(ERROR) << "Unknown container type: " << typeName; + return false; + } + + std::vector paramIdxs; + if (containerInfo->underlyingContainerIndex.has_value()) { + if (containerInfo->numTemplateParams.has_value()) { + LOG(ERROR) << "Container adapters should not enumerate their template " + "parameters"; + return false; + } + paramIdxs.push_back(*containerInfo->underlyingContainerIndex); + } else { + auto numTemplateParams = containerInfo->numTemplateParams; + if (!numTemplateParams.has_value()) { + if (!drgn_type_has_template_parameters(type)) { + LOG(ERROR) << "Failed to find template params"; + return false; + } + numTemplateParams = drgn_type_num_template_parameters(type); + VLOG(1) << "Num template params for " << typeName << " " + << *numTemplateParams; + } + + for (size_t i = 0; i < *numTemplateParams; i++) { + paramIdxs.push_back(i); + } + } + + return enumerateTemplateParamIdxs(type, *containerInfo, paramIdxs, ifStub); +} + +bool OICodeGen::enumerateTemplateParamIdxs(struct drgn_type *type, + const ContainerInfo &containerInfo, + const std::vector ¶mIdxs, + bool &ifStub) { + if (paramIdxs.empty()) { + return true; + } + + auto maxParamIdx = *std::max_element(paramIdxs.begin(), paramIdxs.end()); + if (!drgn_type_has_template_parameters(type) || + drgn_type_num_template_parameters(type) <= maxParamIdx) { + LOG(ERROR) << "Failed to find template params"; + return false; + } + + struct drgn_type_template_parameter *tParams = + drgn_type_template_parameters(type); + for (auto i : paramIdxs) { + struct drgn_qualified_type t; + struct drgn_error *err = drgn_template_parameter_type(&tParams[i], &t); + if (err) { + LOG(ERROR) << "Error when looking up template parameter " << err->code + << " " << err->message; + return false; + } + + if (drgn_type_kind(t.type) != DRGN_TYPE_VOID && + !isDrgnSizeComplete(t.type)) { + ifStub = true; + return true; + } + } + + for (auto i : paramIdxs) { + struct drgn_qualified_type t; + struct drgn_error *err = drgn_template_parameter_type(&tParams[i], &t); + if (err) { + LOG(ERROR) << "Error when looking up template parameter " << err->code + << " " << err->message; + return false; + } + // TODO: This is painful, there seems to be a bug in drgn (or maybe it is + // intentional). Consider a case :- + // typedef struct {int a;} A; + // unique_ptr ptr; + // + // If you query template parameter type of unique_ptr, it skips typdefs + // and directly seems to return underlying type i.e. an unnamed struct. + // Unfortunately, we might need special handling for this. + + if (!OICodeGen::enumerateTypesRecurse(t.type)) { + return false; + } + } + + // Do not add `shared_ptr` or `unique_ptr` + // to `containerTypeMapDrgn`. + if (containerInfo.ctype == SHRD_PTR_TYPE || + containerInfo.ctype == UNIQ_PTR_TYPE) { + struct drgn_qualified_type t {}; + // We checked that this succeeded in the previous loop + drgn_template_parameter_type(&tParams[0], &t); + if (drgn_type_kind(t.type) == DRGN_TYPE_VOID) { + return true; + } + } + + auto &templateTypes = + containerTypeMapDrgn + .emplace(type, std::pair(containerInfo, + std::vector())) + .first->second.second; + + for (auto i : paramIdxs) { + struct drgn_qualified_type t {}; + struct drgn_error *err = drgn_template_parameter_type(&tParams[i], &t); + if (err) { + LOG(ERROR) << "Error when looking up template parameter " << err->code + << " " << err->message; + return false; + } + + // TODO: Any reason this can't be done in the prior loop. Then + // this loop can be deleted + templateTypes.push_back(t); + } + + return true; +} + +void OICodeGen::addPaddingForBaseClass(struct drgn_type *type, + std::vector &def) { + if (drgn_type_num_members(type) < 1) { + return; + } + + struct drgn_type_member *members = drgn_type_members(type); + + VLOG(2) << "Base member offset is " << members[0].bit_offset / CHAR_BIT; + + uint64_t byteOffset = members[0].bit_offset / CHAR_BIT; + + if (byteOffset > 0) { + std::string memName = + std::string("__") + "parentClass[" + std::to_string(byteOffset) + "]"; + std::string tname = "uint8_t"; + + def.push_back(tname); + def.push_back(memName); + def.push_back(";"); + } +} + +std::string_view OICodeGen::drgnKindStr(struct drgn_type *type) { + switch (drgn_type_kind(type)) { + case DRGN_TYPE_VOID: + return "DRGN_TYPE_VOID"; + case DRGN_TYPE_INT: + return "DRGN_TYPE_INT"; + case DRGN_TYPE_BOOL: + return "DRGN_TYPE_BOOL"; + case DRGN_TYPE_FLOAT: + return "DRGN_TYPE_FLOAT"; + case DRGN_TYPE_STRUCT: + return "DRGN_TYPE_STRUCT"; + case DRGN_TYPE_UNION: + return "DRGN_TYPE_UNION"; + case DRGN_TYPE_CLASS: + return "DRGN_TYPE_CLASS"; + case DRGN_TYPE_ENUM: + return "DRGN_TYPE_ENUM"; + case DRGN_TYPE_TYPEDEF: + return "DRGN_TYPE_TYPEDEF"; + case DRGN_TYPE_POINTER: + return "DRGN_TYPE_POINTER"; + case DRGN_TYPE_ARRAY: + return "DRGN_TYPE_ARRAY"; + case DRGN_TYPE_FUNCTION: + return "DRGN_TYPE_FUNCTION"; + } + return ""; +} + +std::string OICodeGen::getAnonName(struct drgn_type *type, + const char *template_) { + std::string typeName; + if (drgn_type_tag(type) != nullptr) { + typeName = drgn_type_tag(type); + } else { + // Unnamed struct/union + if (auto it = unnamedUnion.find(type); it != std::end(unnamedUnion)) { + typeName = it->second; + } else { + typeName = template_ + std::to_string(unnamedUnion.size()); + unnamedUnion.emplace(type, typeName); + // Leak a copy of the typeName to ensure its lifetime is greater than the + // drgn_type + char *typeNameCstr = new char[typeName.size() + 1]; + std::copy(std::begin(typeName), std::end(typeName), typeNameCstr); + typeNameCstr[typeName.size()] = '\0'; + type->_private.tag = typeNameCstr; + // We might need a second copy of the string to avoid double-free + type->_private.oi_name = typeNameCstr; + } + } + return transformTypeName(type, typeName); +} + +bool OICodeGen::getMemberDefinition(struct drgn_type *type) { + // Do a [] lookup to ensure `type` has a entry in classMembersMap + // If it has no entry, the lookup will default construct on for us + classMembersMap[type]; + + structDefType.push_back(type); + + std::string outName; + getDrgnTypeNameInt(type, outName); + VLOG(1) << "Adding members to class " << outName << " " << type; + + // If the type is a union, don't expand the members + if (drgn_type_kind(type) == DRGN_TYPE_UNION) { + return true; + } + + struct drgn_type_member *members = drgn_type_members(type); + for (size_t i = 0; i < drgn_type_num_members(type); i++) { + auto &member = members[i]; + auto memberName = member.name ? std::string(member.name) + : "__anon_member_" + std::to_string(i); + + struct drgn_qualified_type t {}; + uint64_t bitFieldSize = 0; + if (auto *err = drgn_member_type(&member, &t, &bitFieldSize)) { + LOG(ERROR) << "Error when looking up member type '" << memberName + << "': (" << err->code << ") " << err->message; + drgn_error_destroy(err); + continue; + } + + if (drgn_type_kind(t.type) == DRGN_TYPE_FUNCTION) { + continue; + } + + std::string tname; + getDrgnTypeNameInt(t.type, tname); + isKnownType(tname, tname); + + bool isStubbed = isMemberToStub(outName, memberName).has_value(); + + VLOG(2) << "Adding member; type: " << tname << " " << type + << " name: " << memberName << " offset(bits): " << member.bit_offset + << " offset(bytes): " << (float)member.bit_offset / (float)CHAR_BIT + << " isStubbed: " << (isStubbed ? "true" : "false"); + + classMembersMap[type].push_back(DrgnClassMemberInfo{ + t.type, memberName, member.bit_offset, bitFieldSize, isStubbed}); + } + + return true; +} + +void OICodeGen::printTypePath() { + int cnt = 1; + for (auto &type : typePath) { + std::string outName; + getDrgnTypeNameInt(type, outName); + outName = std::string(cnt, ' ') + outName; + VLOG(2) << outName; + cnt += 1; + } +} + +std::string OICodeGen::typeToTransformedName(struct drgn_type *type) { + auto typeName = typeToName(type); + typeName = transformTypeName(type, typeName); + return typeName; +} + +std::string OICodeGen::typeToName(struct drgn_type *type) { + std::string typeName; + if (drgn_type_has_tag(type)) { + const char *typeTag = drgn_type_tag(type); + if (typeTag != nullptr) { + typeName = typeTag; + } else { + typeName = type->_private.oi_name; + } + // TODO: Lookup unnamed union in type->string flag + } else if (drgn_type_has_name(type)) { + typeName = drgn_type_name(type); + } else if (drgn_type_kind(type) == DRGN_TYPE_POINTER) { + char *defStr = nullptr; + struct drgn_qualified_type qtype = {type, {}}; + if (drgn_format_type_name(qtype, &defStr) != nullptr) { + LOG(ERROR) << "Failed to get formatted string for " << type; + typeName = ""; + } else { + typeName.assign(defStr); + free(defStr); + } + } else if (drgn_type_kind(type) == DRGN_TYPE_VOID) { + return "void"; + } else if (drgn_type_kind(type) == DRGN_TYPE_ARRAY) { + size_t elems = 1; + struct drgn_type *arrayElementType = nullptr; + getDrgnArrayElementType(type, &arrayElementType, elems); + + if (drgn_type_has_name(arrayElementType)) { + typeName = drgn_type_name(arrayElementType); + } else if (drgn_type_has_tag(arrayElementType)) { + typeName = drgn_type_tag(arrayElementType); + } else { + LOG(ERROR) << "Failed4 to get typename "; + return ""; + } + } else { + LOG(ERROR) << "Failed3 to get typename "; + return ""; + } + return typeName; +} + +// The top level function which enumerates the rootType object. This function +// fills out : - +// 1. struct/class definitions +// 2. function forward declarations +// 3. function definitions +// +// They are appended to the jit code in that order (1), (2), (3) +bool OICodeGen::populateDefsAndDecls() { + if (drgn_type_has_type(rootType.type) && + drgn_type_kind(rootType.type) == DRGN_TYPE_FUNCTION) { + rootType = drgn_type_type(rootType.type); + } + + auto *type = rootType.type; + rootTypeToIntrospect = rootType; + + struct drgn_qualified_type qtype {}; + if (drgn_type_kind(type) == DRGN_TYPE_POINTER) { + qtype = drgn_type_type(type); + type = qtype.type; + rootTypeToIntrospect = qtype; + } + + auto typeName = typeToTransformedName(type); + if (typeName.empty()) { + return false; + } + if (typeName == "void") { + LOG(ERROR) << "Argument to be introspected cannot be of type void"; + return false; + } + + rootTypeStr = typeName; + VLOG(1) << "Root type to introspect : " << rootTypeStr; + + typePath.push_back(rootType.type); + + return enumerateTypesRecurse(rootType.type); +} + +std::optional OICodeGen::getDrgnTypeSize(struct drgn_type *type) { + uint64_t sz = 0; + if (auto *err = drgn_type_sizeof(type, &sz)) { + LOG(ERROR) << "dgn_type_sizeof(" << type << "): " << err->code << " " + << err->message; + drgn_error_destroy(err); + + auto typeName = getNameForType(type); + if (!typeName.has_value()) { + return std::nullopt; + } + + for (auto &e : sizeMap) { + if (typeName->starts_with(e.first)) { + VLOG(1) << "Looked up " << *typeName << " in sizeMap"; + return e.second; + } + } + + LOG(ERROR) << "Failed to get size for " << *typeName << " " << type; + return std::nullopt; + } + return sz; +} + +bool OICodeGen::isDrgnSizeComplete(struct drgn_type *type) { + uint64_t sz = 0; + auto *err = drgn_type_sizeof(type, &sz); + if (err == nullptr) { + return true; + } + + if (drgn_type_kind(type) != DRGN_TYPE_ENUM) { + std::string name; + getDrgnTypeNameInt(type, name); + + for (auto &kv : sizeMap) { + if (name.starts_with(kv.first)) { + return true; + } + } + VLOG(1) << "Failed to lookup size " << type << " " << name; + } + return false; +} + +struct drgn_type *OICodeGen::drgnUnderlyingType(struct drgn_type *type) { + auto *underlyingType = type; + + while (drgn_type_kind(underlyingType) == DRGN_TYPE_TYPEDEF) { + underlyingType = drgn_type_type(underlyingType).type; + } + + return underlyingType; +} + +bool OICodeGen::enumerateClassParents(struct drgn_type *type, + const std::string &typeName) { + struct drgn_type_template_parameter *parents = drgn_type_parents(type); + + for (size_t i = 0; i < drgn_type_num_parents(type); i++) { + struct drgn_qualified_type t {}; + + if (auto *err = drgn_template_parameter_type(&parents[i], &t)) { + LOG(ERROR) << "Error when looking up parent class for type " << type + << " err " << err->code << " " << err->message; + drgn_error_destroy(err); + return false; + } + + VLOG(2) << "Lookup parent for " << typeName << " " << type; + + if (!isDrgnSizeComplete(t.type)) { + VLOG(2) << "Parent of " << typeName << " " << type << " is " << t.type + << " which is incomplete"; + return false; + } + + if (!OICodeGen::enumerateTypesRecurse(t.type)) { + return false; + } + + parentClasses[type].push_back({t.type, parents[i].bit_offset}); + } + + std::sort(parentClasses[type].begin(), parentClasses[type].end()); + + return true; +} + +bool OICodeGen::enumerateClassMembers(struct drgn_type *type, + const std::string &typeName, + bool &isStubbed) { + struct drgn_type_member *members = drgn_type_members(type); + + for (size_t i = 0; i < drgn_type_num_members(type); i++) { + struct drgn_qualified_type t {}; + auto *err = drgn_member_type(&members[i], &t, nullptr); + + if (err != nullptr || !isDrgnSizeComplete(t.type)) { + if (err != nullptr) { + LOG(ERROR) << "Error when looking up member type " << err->code << " " + << err->message << " " << typeName << " " << members[i].name; + } + VLOG(1) << "Type " << typeName + << " has an incomplete member; stubbing..."; + knownDummyTypeList.insert(type); + isStubbed = true; + return true; + } + + std::string memberName; + if (members[i].name != nullptr) { + memberName.assign(members[i].name); + } + + std::string outName; + getDrgnTypeNameInt(t.type, outName); + VLOG(2) << "Processing member; type: " << outName << " " << t.type + << " name: " << memberName; + + if (!OICodeGen::enumerateTypesRecurse(t.type)) { + return false; + } + } + + return true; +} + +bool OICodeGen::enumerateClassTemplateParams(struct drgn_type *type, + const std::string &typeName, + bool &isStubbed) { + bool ifStub = false; + if (!getContainerTemplateParams(type, ifStub)) { + LOG(ERROR) << "Failed to get container template params"; + return false; + } + + if (ifStub) { + VLOG(1) << "Type " << typeName + << " has an incomplete size member in template params; stubbing..."; + knownDummyTypeList.insert(type); + isStubbed = true; + return true; + } + + auto containerInfo = getContainerInfo(type); + assert(containerInfo.has_value()); + containerTypesFuncDef.insert(*containerInfo); + return true; +} + +bool OICodeGen::ifGenerateMemberDefinition(const std::string &typeName) { + return !isKnownType(typeName); +} + +bool OICodeGen::generateMemberDefinition(struct drgn_type *type, + std::string &typeName) { + if (!getMemberDefinition(type)) { + return false; + } + + /* Unnamed type */ + if (typeName.empty()) { + if (auto it = unnamedUnion.find(type); it != unnamedUnion.end()) { + typeName = it->second; + } else { + LOG(ERROR) << "Failed to find type in unnamedUnion"; + return false; + } + + uint64_t sz = 0; + auto *err = drgn_type_sizeof(type, &sz); + if (err != nullptr) { + LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message; + return false; + } + } + return true; +} + +std::optional> +OICodeGen::isMemberToStub(const std::string &typeName, + const std::string &member) { + auto it = std::ranges::find_if(config.membersToStub, [&](auto &s) { + return typeName.starts_with(s.first) && s.second == member; + }); + if (it == std::end(config.membersToStub)) { + return std::nullopt; + } + return *it; +} + +std::optional OICodeGen::isTypeToStub( + const std::string &typeName) { + if (auto opt = isMemberToStub(typeName, "*")) { + return std::ref(opt.value().first); + } + return std::nullopt; +} + +bool OICodeGen::isTypeToStub(struct drgn_type *type, + const std::string &typeName) { + if (isTypeToStub(typeName)) { + VLOG(1) << "Found type to stub "; + knownDummyTypeList.insert(type); + return true; + } + + return false; +} + +bool OICodeGen::isEmptyClassOrFunctionType(struct drgn_type *type, + const std::string &typeName) { + return (!isKnownType(typeName) && drgn_type_has_members(type) && + drgn_type_num_members(type) == 0); +} + +bool OICodeGen::enumerateClassType(struct drgn_type *type) { + std::string typeName = getStructName(type); + VLOG(2) << "Transformed typename: " << typeName << " " << type; + + if (isTypeToStub(type, typeName)) { + return true; + } + + if (isKnownType(typeName)) { + funcDefTypeList.insert(type); + return true; + } + + if (!(isContainer(type) || isKnownType(typeName))) { + if (!enumerateClassParents(type, typeName)) { + knownDummyTypeList.insert(type); + return true; + } + + bool isStubbed = false; + VLOG(2) << "Processing members for class/union : " << typeName << " " + << type; + if (!enumerateClassMembers(type, typeName, isStubbed)) { + return false; + } + if (isStubbed) { + return true; + } + } + + if (isContainer(type)) { + bool isStubbed = false; + VLOG(1) << "Processing template params container: " << typeName << " " + << type; + + if (!enumerateClassTemplateParams(type, typeName, isStubbed)) { + return false; + } + if (isStubbed) { + return true; + } + } else if (ifGenerateMemberDefinition(typeName)) { + if (!generateMemberDefinition(type, typeName)) { + return false; + } + } else if (isEmptyClassOrFunctionType(type, typeName)) { + knownDummyTypeList.insert(type); + VLOG(2) << "Empty class/function type " << type << " name " << typeName; + return true; + } + + funcDefTypeList.insert(type); + return true; +} + +bool OICodeGen::enumerateTypeDefType(struct drgn_type *type) { + std::string typeName; + if (drgn_type_has_name(type)) { + typeName = drgn_type_name(type); + } + typeName = transformTypeName(type, typeName); + VLOG(2) << "Transformed typename: " << typeName << " " << type; + + if (isTypeToStub(type, typeName)) { + return true; + } + + if (isKnownType(typeName)) { + funcDefTypeList.insert(type); + return true; + } + + auto qtype = drgn_type_type(type); + bool ret = enumerateTypesRecurse(qtype.type); + std::string tname; + + if (drgn_type_has_tag(qtype.type)) { + if (drgn_type_tag(qtype.type) != nullptr) { + tname = drgn_type_tag(qtype.type); + } else { + if (drgn_type_kind(qtype.type) == DRGN_TYPE_UNION) { + tname = getUnionName(qtype.type); + } else { + tname = getStructName(qtype.type); + } + + typeName = tname; + funcDefTypeList.insert(type); + } + } else if (drgn_type_has_name(qtype.type)) { + tname = drgn_type_name(qtype.type); + } else { + uint64_t sz = 0; + auto *err = drgn_type_sizeof(type, &sz); + if (err != nullptr) { + LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message + << " " << typeName; + return false; + } + + if (sz == sizeof(uintptr_t)) { + // This is a typedef'ed pointer + tname = "uintptr_t"; + } else { + LOG(ERROR) << "Failed to get typename: kind " << drgnKindStr(type); + return false; + } + } + + typedefMap[typeName] = transformTypeName(qtype.type, tname); + typedefTypes[type] = qtype.type; + + return ret; +} + +bool OICodeGen::enumerateEnumType(struct drgn_type *type) { + std::string typeName; + + if (drgn_type_tag(type) != nullptr) { + typeName.assign(drgn_type_tag(type)); + } else { + typeName = "UNNAMED"; + } + + if (isKnownType(typeName)) { + return true; + } + + std::string enumUnderlyingTypeStr; + if (!getEnumUnderlyingTypeStr(type, enumUnderlyingTypeStr)) { + return false; + } + VLOG(2) << "Converting enum " << enumUnderlyingTypeStr << " " << type + << " tag: " << typeName; + + enumTypes.push_back(type); + funcDefTypeList.insert(type); + + return true; +} + +static struct drgn_type *getPtrUnderlyingType(struct drgn_type *type) { + struct drgn_type *underlyingType = type; + + while (drgn_type_kind(underlyingType) == DRGN_TYPE_POINTER || + drgn_type_kind(underlyingType) == DRGN_TYPE_TYPEDEF) { + underlyingType = drgn_type_type(underlyingType).type; + } + + return underlyingType; +} + +bool OICodeGen::enumeratePointerType(struct drgn_type *type) { + // Not handling pointers right now. Pointer members in classes are going to be + // tricky. If we enumerate objects from pointers there are many questions :- + // 1. How to handle uninitialized pointers + // 2. How to handle nullptr pointers + // 3. How to handle cyclical references with pointers + // Very common for two structs/classes to have pointers to each other + // We will need to save previously encountered pointer values + // 4. Smart pointers might make it easier to detect (1)/(2) + + bool ret = true; + struct drgn_qualified_type qtype = drgn_type_type(type); + + funcDefTypeList.insert(type); + + // If type is a function pointer, directly store the underlying type in + // pointerToTypeMap, so that TreeBuilder can easily replace function + // pointers with uintptr_t + struct drgn_type *utype = getPtrUnderlyingType(type); + if (drgn_type_kind(utype) == DRGN_TYPE_FUNCTION) { + VLOG(2) << "Type " << type << " is a function pointer to " << utype; + pointerToTypeMap.emplace(type, utype); + return ret; + } + + pointerToTypeMap.emplace(type, qtype.type); + struct drgn_type *underlyingType = drgnUnderlyingType(qtype.type); + + bool isComplete = isDrgnSizeComplete(underlyingType); + if (drgn_type_kind(underlyingType) == DRGN_TYPE_FUNCTION || isComplete) { + ret = enumerateTypesRecurse(qtype.type); + } else if (!isComplete && drgn_type_kind(underlyingType) != DRGN_TYPE_VOID) { + // If the underlying type is not complete create a stub for the pointer type + knownDummyTypeList.insert(type); + } + + return ret; +} + +bool OICodeGen::enumeratePrimitiveType(struct drgn_type *type) { + std::string typeName; + + if (!drgn_type_has_name(type)) { + LOG(ERROR) << "Expected type to have a name: " << type; + return false; + } + + typeName = drgn_type_name(type); + typeName = transformTypeName(type, typeName); + + funcDefTypeList.insert(type); + return true; +} + +bool OICodeGen::enumerateArrayType(struct drgn_type *type) { + uint64_t ret = 0; + + auto *err = drgn_type_sizeof(type, &ret); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up size from drgn " << err->code << " " + << err->message << " " << std::endl; + return false; + } + + auto qtype = drgn_type_type(type); + auto rval = enumerateTypesRecurse(qtype.type); + + VLOG(1) << "Array type sizeof " << rval << " len " << drgn_type_length(type); + + return true; +} + +bool OICodeGen::enumerateTypesRecurse(struct drgn_type *type) { + auto kind = drgn_type_kind(type); + + if (kind == DRGN_TYPE_VOID || kind == DRGN_TYPE_FUNCTION) { + VLOG(1) << "Ignore type " << drgnKindStr(type); + return true; + } + + // We don't want to generate functions for the same type twice + if (processedTypes.find(type) != processedTypes.end()) { + VLOG(2) << "Already encountered " << type; + return true; + } + + std::string outName; + getDrgnTypeNameInt(type, outName); + + struct drgn_qualified_type qtype = {type, {}}; + char *defStr = nullptr; + std::string typeDefStr; + + if (drgn_format_type(qtype, &defStr) != nullptr) { + LOG(ERROR) << "Failed to get formatted string for " << type; + typeDefStr.assign("unknown"); + } else { + typeDefStr.assign(defStr); + free(defStr); + } + + VLOG(1) << "START processing type: " << outName << " " << type << " " + << drgnKindStr(type) << " {"; + g_level += 1; + + VLOG(2) << typeDefStr; + + typePath.push_back(type); + printTypePath(); + + processedTypes.insert(type); + + bool ret = true; + + switch (kind) { + case DRGN_TYPE_CLASS: + case DRGN_TYPE_STRUCT: + case DRGN_TYPE_UNION: + ret = enumerateClassType(type); + break; + case DRGN_TYPE_ENUM: + ret = enumerateEnumType(type); + break; + case DRGN_TYPE_TYPEDEF: + ret = enumerateTypeDefType(type); + break; + case DRGN_TYPE_POINTER: + ret = enumeratePointerType(type); + break; + case DRGN_TYPE_ARRAY: + enumerateArrayType(type); + break; + case DRGN_TYPE_INT: + case DRGN_TYPE_BOOL: + case DRGN_TYPE_FLOAT: + ret = enumeratePrimitiveType(type); + break; + + default: + LOG(ERROR) << "Unknown drgn type " << type; + return false; + } + g_level -= 1; + + typePath.pop_back(); + + VLOG(1) << "END processing type: " << outName << " " << type << " " + << drgnKindStr(type) << " ret:" << ret << " }"; + + return ret; +} + +std::optional OICodeGen::getNameForType(struct drgn_type *type) { + if (typeToNameMap.find(type) == typeToNameMap.end()) { + LOG(ERROR) << "QOO7 Failed to find " << type; + return std::nullopt; + } + return typeToNameMap[type]; +} + +void OICodeGen::getFuncDefClassMembers( + std::string &code, struct drgn_type *type, + std::unordered_map &memberNames, bool skipPadding) { + if (drgn_type_kind(type) == DRGN_TYPE_TYPEDEF) { + // Handle case where parent is a typedef + getFuncDefClassMembers(code, drgnUnderlyingType(type), memberNames); + return; + } + + if (parentClasses.find(type) != parentClasses.end()) { + for (auto &p : parentClasses[type]) { + // paddingIndexMap[type] already cover the parents' paddings, + // so skip the parents' padding generation to avoid double counting + getFuncDefClassMembers(code, p.type, memberNames, true); + } + } + + if (classMembersMap.find(type) == classMembersMap.end()) { + return; + } + + if (!skipPadding) { + auto paddingIt = paddingIndexMap.find(type); + if (paddingIt != paddingIndexMap.end()) { + const auto &paddingRange = paddingIt->second; + for (auto i = paddingRange.first; i < paddingRange.second; i++) { + code += "SAVE_SIZE(sizeof(t.__padding_" + std::to_string(i) + "));\n"; + } + } + } + + const auto &members = classMembersMap[type]; + + bool captureThriftIsset = thriftIssetStructTypes.contains(type); + if (captureThriftIsset) { + assert(members[members.size() - 1].member_name == "__isset"); + auto name = *fullyQualifiedName(type); + code += "using thrift_data = apache::thrift::TStructDataStorage<"; + code += name; + code += ">;\n"; + } + + for (std::size_t i = 0; i < members.size(); i++) { + if (captureThriftIsset && i < members.size() - 1) { + // Capture Thrift's isset value for each field, except __isset itself, + // which we assume comes last + assert(members[i].member_name != "__isset"); + std::string issetIdxStr = + "thrift_data::isset_indexes[" + std::to_string(i) + "]"; + code += "{\n"; + code += " if (&thrift_data::isset_indexes != nullptr &&\n"; + code += " " + issetIdxStr + " != -1) {\n"; + code += " SAVE_DATA(t.__isset.get(" + issetIdxStr + "));\n"; + code += " } else {\n"; + code += " SAVE_DATA(-1);\n"; + code += " }\n"; + code += "}\n"; + } + + const auto &member = members[i]; + std::string memberName = member.member_name; + std::replace(memberName.begin(), memberName.end(), '.', '_'); + + deduplicateMemberName(memberNames, memberName); + + if (memberName.empty()) { + continue; + } + /* + * Check if the member is a bit field (bitFieldSize > 0). + * If it is a bit field, we can't get its address with operator&. + * If it is *NOT* a bit field, then we can print its address. + */ + if (member.bit_field_size > 0) { + code += "JLOG(\"" + memberName + " (bit_field)\\n\");\n"; + } else { + code += "JLOG(\"" + memberName + " @\");\n"; + code += "JLOGPTR(&t." + memberName + ");\n"; + } + + code += "getSizeType(t." + memberName + ", returnArg);\n"; + } +} + +void OICodeGen::getFuncDefinitionStr(std::string &code, struct drgn_type *type, + const std::string &typeName) { + if (classMembersMap.find(type) == classMembersMap.end()) { + return; + } + + code += std::string("void getSizeType(const ") + typeName + + std::string("& t, size_t& returnArg) {\n"); + + std::unordered_map memberNames; + getFuncDefClassMembers(code, type, memberNames); + + code += "}\n"; +} + +std::string OICodeGen::templateTransformType(const std::string &typeName) { + std::string s; + s.reserve(typeName.size()); + for (const auto &c : typeName) { + if (c == '<' || c == '>' || c == ',' || c == ' ' || c == ':' || c == '(' || + c == ')' || c == '&' || c == '*' || c == '-' || c == '\'' || c == '[' || + c == ']') { + s += '_'; + } else { + s += c; + } + } + return s; +} + +std::string OICodeGen::structNameTransformType(const std::string &typeName) { + std::string s; + bool prevColon = false; + + s.reserve(typeName.size()); + for (const auto &c : typeName) { + if (c == ':') { + if (prevColon) { + prevColon = false; + } else { + prevColon = true; + } + } else { + if (prevColon) { + s[s.size() - 1] = '_'; + } + prevColon = false; + } + + std::string valid_characters = " &*<>[](){},;:\n"; + if (std::isalnum(c) || valid_characters.find(c) != std::string::npos) { + s += c; + } else { + s += '_'; + } + } + return s; +} + +void OICodeGen::memberTransformName( + std::map &templateTransformMap, + std::string &typeName) { + std::vector sortedTypes; + + for (auto &e : templateTransformMap) { + sortedTypes.push_back(e.first); + } + + std::sort(sortedTypes.begin(), sortedTypes.end(), + [](const std::string &first, const std::string &second) { + return first.size() > second.size(); + }); + + for (auto &e : sortedTypes) { + std::string search = e; + std::string replace = templateTransformMap[e]; + boost::replace_all(typeName, search, replace); + } +} + +OICodeGen::SortedTypeDefMap OICodeGen::getSortedTypeDefMap( + const std::map &typedefTypeMap) { + auto typeMap = typedefTypeMap; + SortedTypeDefMap typedefVec; + + while (!typeMap.empty()) { + for (auto it = typeMap.cbegin(); it != typeMap.cend();) { + if (typeMap.find(it->second) == typeMap.end()) { + typedefVec.push_back(std::make_pair(it->first, it->second)); + it = typeMap.erase(it); + } else { + ++it; + } + } + } + return typedefVec; +} + +bool OICodeGen::getEnumUnderlyingTypeStr(struct drgn_type *e, + std::string &enumUnderlyingTypeStr) { + std::string name; + if (drgn_type_tag(e) != nullptr) { + name.assign(drgn_type_tag(e)); + } else { + VLOG(2) << "Enum tag lookup failed"; + } + + uint64_t sz = 0; + auto *err = drgn_type_sizeof(e, &sz); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up size from drgn " << err->code << " " + << err->message << " "; + return false; + } + + VLOG(2) << "Enum " << name << " size " << sz; + + switch (sz) { + case 8: + enumUnderlyingTypeStr = "uint64_t"; + break; + case 4: + enumUnderlyingTypeStr = "uint32_t"; + break; + case 2: + enumUnderlyingTypeStr = "uint16_t"; + break; + case 1: + enumUnderlyingTypeStr = "uint8_t"; + break; + default: + LOG(ERROR) << "Error invalid enum size " << name << " " << sz; + return false; + } + + return true; +} + +bool OICodeGen::getDrgnTypeNameInt(struct drgn_type *type, + std::string &outName) { + std::string name; + + if (drgn_type_kind(type) == DRGN_TYPE_ENUM) { + if (!getEnumUnderlyingTypeStr(type, name)) { + return false; + } + } else if (drgn_type_has_tag(type)) { + if (drgn_type_tag(type) != nullptr) { + name.assign(drgn_type_tag(type)); + } else { + if (drgn_type_kind(type) == DRGN_TYPE_UNION) { + name = getUnionName(type); + } else { + name = getStructName(type); + } + } + } else if (drgn_type_has_name(type)) { + name.assign(drgn_type_name(type)); + } else if (drgn_type_kind(type) == DRGN_TYPE_POINTER) { + struct drgn_type *underlyingType = getPtrUnderlyingType(type); + if (config.chaseRawPointers && + drgn_type_kind(underlyingType) != DRGN_TYPE_FUNCTION) { + // For pointers, figure out name for the underlying type then add + // appropriate number of '*' + { + int ptrDepth = 0; + struct drgn_type *ut = type; + while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) { + ut = drgn_type_type(ut).type; + ptrDepth++; + } + + std::string tmpName; + if (!getDrgnTypeNameInt(ut, tmpName)) { + LOG(ERROR) << "Failed to get name for type " << type; + return false; + } + outName = tmpName + std::string(ptrDepth, '*'); + return true; + } + } else { + name.assign("uintptr_t"); + } + } else if (drgn_type_kind(type) == DRGN_TYPE_VOID) { + name.assign("void"); + } else { + VLOG(2) << "Failed to get tag/name for type " << type; + return false; + } + + name = transformTypeName(type, name); + name = structNameTransformType(name); + outName = name; + + return true; +} + +bool OICodeGen::getDrgnTypeName(struct drgn_type *type, std::string &outName) { + return getDrgnTypeNameInt(type, outName); +} + +void OICodeGen::addTypeToName(struct drgn_type *type, std::string name) { + VLOG(2) << "Trying to assign name to type: " << name << " " << type; + + if (typeToNameMap.find(type) != typeToNameMap.end()) { + VLOG(2) << "Name already assigned to type: " << name << " " << type; + return; + } + + name = structNameTransformType(name); + + if (nameToTypeMap.find(name) != nameToTypeMap.end()) { + VLOG(1) << "Name conflict : " << type << " " << name << " " + << drgnKindStr(type) << " conflict with " << nameToTypeMap[name] + << " " << drgnKindStr(nameToTypeMap[name]); + + if (std::ranges::find(structDefType, type) != structDefType.end() || + std::ranges::find(enumTypes, type) != enumTypes.end() || + typedefTypes.find(type) != typedefTypes.end() || + knownDummyTypeList.find(type) != knownDummyTypeList.end()) { + int tIndex = 0; + // Name clashes with another type. Attach an ID at the end and make sure + // that name isn't already present for some other drgn type. + // + while (nameToTypeMap.find(name + "__" + std::to_string(tIndex)) != + nameToTypeMap.end()) { + tIndex++; + } + + if (name != "uint8_t" && name != "uint32_t") { + name = name + "__" + std::to_string(tIndex); + } else { + VLOG(1) << "Not renaming " << name; + } + } + } + + VLOG(1) << "Assigned name to type: " << name << " " << type; + + typeToNameMap[type] = name; + nameToTypeMap[name] = type; +} + +void OICodeGen::getClassMembersIncludingParent( + struct drgn_type *type, std::vector &out) { + if (drgn_type_kind(type) == DRGN_TYPE_TYPEDEF) { + // Handle case where parent is a typedef + getClassMembersIncludingParent(drgnUnderlyingType(type), out); + return; + } + + if (parentClasses.find(type) != parentClasses.end()) { + for (auto &parent : parentClasses[type]) { + getClassMembersIncludingParent(parent.type, out); + } + } + + for (auto &mem : classMembersMap[type]) { + out.push_back(mem); + } +} + +std::map> + &OICodeGen::getClassMembersMap() { + for (auto &e : classMembersMap) { + std::vector v; + getClassMembersIncludingParent(e.first, v); + classMembersMapCopy[e.first] = v; + } + + for (auto &e : classMembersMapCopy) { + VLOG(1) << "ClassCopy " << e.first << std::endl; + for (auto &m : e.second) { + VLOG(1) << " " << m.type << " " << m.member_name << std::endl; + } + } + + return classMembersMapCopy; +} + +// 1. First iterate through structs which we have to define (i.e. structDefType +// and knownDummyTypeList) and apply templateTransformType. +// 2. After that, go through typedefList and while assigning name to type, make +// sure it is transformed (i.e. memberTransformName) +// 3. Do the same for funcDefTypeList + +void OICodeGen::printAllTypes() { + VLOG(2) << "Printing all types"; + VLOG(2) << "Classes"; + + for (auto &e : classMembersMap) { + VLOG(2) << "Class " << e.first; + + auto &members = e.second; + for (auto &m : members) { + VLOG(2) << " " << m.member_name << " " << m.type; + } + } + + VLOG(2) << "Struct defs "; + for (auto &e : structDefType) { + VLOG(2) << "Defined struct " << e; + } + + VLOG(2) << "FuncDef structs "; + for (auto &e : funcDefTypeList) { + VLOG(2) << "FuncDef struct " << e; + } + + VLOG(2) << "Dummy structs "; + for (const auto &e : knownDummyTypeList) { + VLOG(2) << "Dummy struct " << e; + } +} + +void OICodeGen::printAllTypeNames() { + VLOG(2) << "Printing all type names "; + VLOG(2) << "Classes"; + + for (auto &e : classMembersMap) { + auto typeName = getNameForType(e.first); + if (!typeName.has_value()) { + continue; + } + + VLOG(2) << "Class " << *typeName << " " << e.first; + auto &members = e.second; + for (auto &m : members) { + VLOG(2) << " " << m.member_name << " " << m.type; + } + } + + for (auto &kv : unnamedUnion) { + VLOG(2) << "Unnamed union/struct " << kv.first << " " << kv.second; + } + + VLOG(2) << "Structs defs "; + for (auto &e : structDefType) { + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + continue; + } + + VLOG(2) << "Defined struct " << *typeName << " " << e; + } + + VLOG(2) << "\nFuncDef structs "; + for (auto &e : funcDefTypeList) { + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + continue; + } + + VLOG(2) << "FuncDef struct " << *typeName << " " << e; + } + + VLOG(2) << "\nDummy structs "; + + for (auto &e : knownDummyTypeList) { + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + continue; + } + + VLOG(2) << "Dummy struct " << *typeName << " " << e; + } +} + +bool OICodeGen::generateStructDef(struct drgn_type *e, std::string &code) { + if (classMembersMap.find(e) == classMembersMap.end()) { + LOG(ERROR) << "Failed to find in classMembersMap " << e; + return false; + } + + auto kind = drgn_type_kind(e); + + if (kind != DRGN_TYPE_STRUCT && kind != DRGN_TYPE_CLASS && + kind != DRGN_TYPE_UNION) { + LOG(ERROR) << "Failed to read type"; + return false; + } + + std::string generatedMembers; + PaddingInfo paddingInfo{}; + bool violatesAlignmentRequirement = false; + auto tmpStr = getNameForType(e); + + if (!tmpStr.has_value()) { + return false; + } + + auto sz = getDrgnTypeSize(e); + if (!sz.has_value()) { + return false; + } + + VLOG(1) << "Generate members for " << *tmpStr << " " << e << " {"; + // paddingIndexMap saves the range of padding indexes used for the current + // struct We save the current pad_index as the start of the range... + auto startingPadIndex = pad_index; + + uint64_t offsetBits = 0; + std::unordered_map memberNames; + if (!generateStructMembers(e, memberNames, generatedMembers, offsetBits, + paddingInfo, violatesAlignmentRequirement, 0)) { + return false; + } + + // After the members have been generated, we save the updated pad_index as the + // end of the range + if (startingPadIndex != pad_index) { + [[maybe_unused]] auto [_, paddingInserted] = + paddingIndexMap.emplace(e, std::make_pair(startingPadIndex, pad_index)); + assert(paddingInserted); + } + + uint64_t offsetBytes = offsetBits / CHAR_BIT; + if (drgn_type_kind(e) != DRGN_TYPE_UNION && *sz != offsetBytes) { + VLOG(1) << "size mismatch " << e << " when generating drgn: " << *sz << " " + << offsetBytes << " " << *tmpStr; + // Special case when class is empty + if (!(offsetBytes == 0 && *sz == 1)) { + } + } else { + VLOG(2) << "Size matches " << *tmpStr << " " << e << " sz " << *sz + << " offsetBytes " << offsetBytes; + } + + std::string structDefinition; + + if (paddingInfo.paddingSize != 0 && config.genPaddingStats) { + structDefinition.append("/* offset | size */"); + } + + if (kind == DRGN_TYPE_STRUCT || kind == DRGN_TYPE_CLASS) { + structDefinition.append("struct"); + } else if (kind == DRGN_TYPE_UNION) { + structDefinition.append("union"); + auto alignment = getAlignmentRequirements(e); + if (!alignment.has_value()) { + return false; + } + structDefinition.append(" alignas(" + + std::to_string(*alignment / CHAR_BIT) + ")"); + } + + if (config.packStructs && + (kind == DRGN_TYPE_STRUCT || kind == DRGN_TYPE_CLASS) && + violatesAlignmentRequirement && paddingInfo.paddingSize == 0) { + structDefinition.append(" __attribute__((__packed__))"); + } + + structDefinition.append(" "); + structDefinition.append(*tmpStr); + structDefinition.append("{\n"); + if (kind == DRGN_TYPE_UNION) { + // Pad out unions + structDefinition.append("char union_padding[" + std::to_string(*sz) + + "];\n"); + } else { + structDefinition.append(generatedMembers); + } + VLOG(1) << "}"; + + structDefinition.append("};\n"); + + if (config.genPaddingStats) { + auto paddedStructFound = paddedStructs.find(*tmpStr); + + if (paddedStructFound == paddedStructs.end()) { + if (paddingInfo.paddingSize || paddingInfo.isSetSize) { + paddingInfo.structSize = offsetBytes; + paddingInfo.paddingSize /= CHAR_BIT; + paddingInfo.definition = structDefinition; + + paddingInfo.computeSaving(); + + if (paddingInfo.savingSize > 0) { + paddedStructs.insert({*tmpStr, paddingInfo}); + } + } + } + } + + code.append(structDefinition); + + // In FuncGen add static_assert for sizes from this array + if (drgn_type_kind(e) != DRGN_TYPE_UNION) { + topoSortedStructTypes.push_back(e); + } + + return true; +} + +void OICodeGen::getDrgnArrayElementType(struct drgn_type *type, + struct drgn_type **outElemType, + size_t &outNumElems) { + size_t elems = 1; + + // for multi dimensional arrays + auto *arrayElementType = type; + while (drgn_type_kind(arrayElementType) == DRGN_TYPE_ARRAY) { + size_t l = drgn_type_length(arrayElementType); + elems *= l; + auto qtype = drgn_type_type(arrayElementType); + arrayElementType = qtype.type; + } + + *outElemType = arrayElementType; + outNumElems = elems; +} + +bool OICodeGen::isNumMemberGreaterThanZero(struct drgn_type *type) { + if (drgn_type_num_members(type) > 0) { + return true; + } + + if (parentClasses.find(type) != parentClasses.end()) { + for (auto &p : parentClasses[type]) { + auto *underlyingType = drgnUnderlyingType(p.type); + if (isNumMemberGreaterThanZero(underlyingType)) { + return true; + } + } + } + + return false; +} + +bool OICodeGen::addPadding(uint64_t padding_bits, std::string &code) { + if (padding_bits == 0) { + return false; + } + + VLOG(1) << "Add padding bits " << padding_bits; + if (padding_bits % CHAR_BIT != 0) { + VLOG(1) << "WARNING: Padding not aligned to a byte"; + } + + if ((padding_bits / CHAR_BIT) > 0) { + std::ostringstream info; + + bool isByteMultiple = padding_bits % CHAR_BIT == 0; + + if (isByteMultiple) { + info << "/* XXX" << std::setw(3) << std::right << padding_bits / CHAR_BIT + << "-byte hole */"; + } else { + info << "/* XXX" << std::setw(3) << std::right << padding_bits + << "-bit hole */"; + } + + code.append(info.str()); + code.append("char __padding_" + std::to_string(pad_index++) + "[" + + std::to_string(padding_bits / CHAR_BIT) + "];\n"); + } + return true; +} + +static inline void addSizeComment(bool genPaddingStats, std::string &code, + size_t offset, size_t sizeInBits) { + if (!genPaddingStats) { + return; + } + + bool isByteMultiple = sizeInBits % CHAR_BIT == 0; + size_t sizeInBytes = (sizeInBits + CHAR_BIT - 1) / CHAR_BIT; + + std::ostringstream info; + if (isByteMultiple) { + info << "/* " << std::setw(10) << std::left << offset / CHAR_BIT << "| " + << std::setw(5) << std::right << sizeInBytes << " */"; + } else { + info << "/* " << std::setw(4) << std::left << offset / CHAR_BIT + << ": 0 | " << std::setw(5) << std::right << sizeInBytes << " */"; + } + code.append(info.str()); +} + +void OICodeGen::deduplicateMemberName( + std::unordered_map &memberNames, + std::string &memberName) { + if (!memberName.empty()) { + auto srchRes = memberNames.find(memberName); + if (srchRes == memberNames.end()) { + memberNames[memberName] = 1; + } else { + auto newCurrentIndex = srchRes->second + 1; + srchRes->second = newCurrentIndex; + memberName += std::to_string(newCurrentIndex); + } + } +} + +std::optional OICodeGen::generateMember( + const DrgnClassMemberInfo &m, + std::unordered_map &memberNames, uint64_t currOffsetBits, + std::string &code, bool isInUnion) { + // Generate unique name for member + std::string memberName = m.member_name; + deduplicateMemberName(memberNames, memberName); + std::replace(memberName.begin(), memberName.end(), '.', '_'); + + auto *memberType = m.type; + auto szBytes = getDrgnTypeSize(memberType); + + if (!szBytes.has_value()) { + return std::nullopt; + } + + if (m.isStubbed) { + code.append("char "); + code.append(memberName); + code.append("["); + code.append(std::to_string(szBytes.value())); + code.append("]; // Member stubbed per config's membersToStub\n"); + return currOffsetBits + 8 * szBytes.value(); + } + + if (drgn_type_kind(memberType) == DRGN_TYPE_ARRAY) { + // TODO: No idea how to handle flexible array member or zero length array + size_t elems = 1; + + struct drgn_type *arrayElementType = nullptr; + getDrgnArrayElementType(memberType, &arrayElementType, elems); + auto tmpStr = getNameForType(arrayElementType); + + if (!tmpStr.has_value()) { + return std::nullopt; + } + + if (elems == 0 || isInUnion) { + std::string memName = memberName + "[" + std::to_string(elems) + "]"; + code.append(*tmpStr); + code.append(" "); + code.append(memName); + code.append(";"); + + code.append("\n"); + + if (isInUnion) { + currOffsetBits = 0; + VLOG(1) << "Member size: " << *szBytes * CHAR_BIT; + } + } else { + code.append("std::array<"); + code.append(*tmpStr); + code.append(", "); + code.append(std::to_string(elems)); + code.append("> "); + code.append(memberName); + code.append(";\n"); + currOffsetBits = currOffsetBits + (*szBytes * CHAR_BIT); + } + } else { + auto tmpStr = getNameForType(memberType); + uint64_t memberSize = 0; + + if (!tmpStr.has_value()) { + return std::nullopt; + } + + if (m.bit_field_size > 0) { + memberSize = m.bit_field_size; + } else { + auto drgnTypeSize = getDrgnTypeSize(memberType); + if (!drgnTypeSize.has_value()) { + return false; + } + memberSize = *drgnTypeSize * CHAR_BIT; + } + + if (isInUnion) { + currOffsetBits = 0; + VLOG(1) << "Member size: " << memberSize; + } else { + currOffsetBits = currOffsetBits + memberSize; + addSizeComment(config.genPaddingStats, code, currOffsetBits, memberSize); + } + + code.append(*tmpStr); + code.append(" "); + code.append(memberName); + if (m.bit_field_size > 0) { + code.append(":" + std::to_string(m.bit_field_size)); + } + code.append(";\n"); + } + return currOffsetBits; +} + +bool OICodeGen::generateParent( + struct drgn_type *p, std::unordered_map &memberNames, + uint64_t &currOffsetBits, std::string &code, size_t offsetToNextMember) { + // Parent class could be a typedef + PaddingInfo paddingInfo{}; + bool violatesAlignmentRequirement = false; + auto *underlyingType = drgnUnderlyingType(p); + uint64_t offsetBits = 0; + + if (!generateStructMembers(underlyingType, memberNames, code, offsetBits, + paddingInfo, violatesAlignmentRequirement, + offsetToNextMember)) { + return false; + } + + // *Sigh* account for base class optimization + auto typeSize = getDrgnTypeSize(underlyingType); + if (!typeSize.has_value()) { + return false; + } + + if (*typeSize > 1 || isNumMemberGreaterThanZero(underlyingType)) { + currOffsetBits += offsetBits; + } else { + VLOG(1) << "Zero members " << p << " skipping parent class " + << underlyingType; + } + + return true; +} + +/*Helper function that returns the alignment constraints in bits*/ +std::optional OICodeGen::getAlignmentRequirements( + struct drgn_type *e) { + const uint64_t minimumAlignmentBits = CHAR_BIT; + uint64_t alignmentRequirement = CHAR_BIT; + std::string outName; + std::optional typeSize; + + switch (drgn_type_kind(e)) { + case DRGN_TYPE_VOID: + case DRGN_TYPE_FUNCTION: + LOG(ERROR) << "Alignment req unhandled " << getDrgnTypeName(e, outName); + return std::nullopt; + case DRGN_TYPE_INT: + case DRGN_TYPE_BOOL: + case DRGN_TYPE_FLOAT: + case DRGN_TYPE_ENUM: + typeSize = getDrgnTypeSize(e); + if (!typeSize.has_value()) { + return std::nullopt; + } + + return *typeSize * CHAR_BIT; + case DRGN_TYPE_STRUCT: + case DRGN_TYPE_CLASS: + case DRGN_TYPE_UNION: + if (isContainer(e)) { + alignmentRequirement = 64; + } else { + auto numMembers = drgn_type_num_members(e); + auto *members = drgn_type_members(e); + for (size_t i = 0; i < numMembers; i++) { + struct drgn_qualified_type memberType {}; + if (drgn_member_type(&members[i], &memberType, nullptr) != nullptr) { + continue; + } + size_t currentMemberAlignmentRequirement = + getAlignmentRequirements(memberType.type) + .value_or(minimumAlignmentBits); + alignmentRequirement = + std::max(alignmentRequirement, currentMemberAlignmentRequirement); + } + for (size_t parentIndex = 0; parentIndex < parentClasses[e].size(); + parentIndex++) { + size_t parentAlignment = + getAlignmentRequirements(parentClasses[e][parentIndex].type) + .value_or(minimumAlignmentBits); + alignmentRequirement = + std::max(alignmentRequirement, parentAlignment); + } + } + + return alignmentRequirement; + case DRGN_TYPE_POINTER: + return 64; + case DRGN_TYPE_TYPEDEF: + case DRGN_TYPE_ARRAY: + auto *underlyingType = drgn_type_type(e).type; + return getAlignmentRequirements(underlyingType); + } + return minimumAlignmentBits; +} + +bool OICodeGen::generateStructMembers( + struct drgn_type *e, std::unordered_map &memberNames, + std::string &code, uint64_t &out_offset_bits, PaddingInfo &paddingInfo, + bool &violatesAlignmentRequirement, size_t offsetToNextMemberInSubclass) { + if (classMembersMap.find(e) == classMembersMap.end()) { + VLOG(1) << "Failed to find type in classMembersMap: " << e; + } + + size_t parentIndex = 0; + size_t memberIndex = 0; + + auto &members = classMembersMap[e]; + + uint64_t currOffsetBits = 0; + uint64_t alignmentRequirement = 8; + + bool hasParents = (parentClasses.find(e) != parentClasses.end()); + + do { + uint64_t memberOffsetBits = ULONG_MAX; + + if (memberIndex < members.size()) { + memberOffsetBits = members[memberIndex].bit_offset; + } + + uint64_t parentOffsetBits = ULONG_MAX; + + if (hasParents && parentIndex < parentClasses[e].size()) { + parentOffsetBits = parentClasses[e][parentIndex].bit_offset; + } + + if (memberOffsetBits == ULONG_MAX && parentOffsetBits == ULONG_MAX) { + break; + } + + // Truncate the typenames while printing. They can be huge. + if (memberOffsetBits < parentOffsetBits) { + std::string typeName; + + if (typeToNameMap.find(members[memberIndex].type) != + typeToNameMap.end()) { + std::optional tmpStr = + getNameForType(members[memberIndex].type); + if (!tmpStr.has_value()) + return false; + + typeName = std::move(*tmpStr); + } + + VLOG(1) << "Add class member type: " << typeName.substr(0, 15) << "... " + << members[memberIndex].type << " currOffset: " << currOffsetBits + << " expectedOffset: " << memberOffsetBits; + + if (drgn_type_kind(e) != DRGN_TYPE_UNION) { + if (currOffsetBits > memberOffsetBits) { + LOG(ERROR) << "Failed to match " << e << " currOffsetBits " + << currOffsetBits << " memberOffsetBits " + << memberOffsetBits; + return false; + } + if (addPadding(memberOffsetBits - currOffsetBits, code)) { + paddingInfo.paddingSize += memberOffsetBits - currOffsetBits; + paddingInfo.paddings.push_back(memberOffsetBits - currOffsetBits); + } + currOffsetBits = memberOffsetBits; + } + + size_t prevOffsetBits = currOffsetBits; + + auto newCurrOffsetBits = + generateMember(members[memberIndex], memberNames, currOffsetBits, + code, drgn_type_kind(e) == DRGN_TYPE_UNION); + + if (!newCurrOffsetBits.has_value()) { + return false; + } + currOffsetBits = *newCurrOffsetBits; + + // Determine whether this type is a Thrift struct. Only try and read the + // isset values if the struct layout is as expected. + // i.e. __isset must be the last member in the struct + const auto &memberName = members[memberIndex].member_name; + bool isThriftIssetStruct = + typeName.starts_with("isset_bitset<") && memberName == "__isset"; + + if (config.captureThriftIsset && isThriftIssetStruct && + memberIndex == members.size() - 1) { + thriftIssetStructTypes.insert(e); + } + + if (config.genPaddingStats) { + paddingInfo.isThriftStruct = isThriftIssetStruct; + + /* + * Thrift has a __isset member for each field to represent that field + * is "set" or not. One byte is normally used for each field(has + * Unpacked in the name) by using @cpp.PackIsset annotation, we use + * one bit for each field instead (has Packed in the name). We're + * only looking for Unpacked __isset as those are the only ones we can + * optimise + */ + if (paddingInfo.isThriftStruct && + typeName.find("Unpacked") != std::string::npos) { + size_t unpackedSize = (currOffsetBits - prevOffsetBits) / CHAR_BIT; + size_t packedSize = (unpackedSize + CHAR_BIT - 1) / CHAR_BIT; + size_t savingFromPacking = unpackedSize - packedSize; + + if (savingFromPacking > 0) { + paddingInfo.isSetSize = unpackedSize; + paddingInfo.isSetOffset = currOffsetBits / CHAR_BIT; + } + } + } + + auto currentMemberAlignmentRequirement = + getAlignmentRequirements(members[memberIndex].type); + + if (!currentMemberAlignmentRequirement.has_value()) { + return false; + } + + alignmentRequirement = + std::max(alignmentRequirement, *currentMemberAlignmentRequirement); + paddingInfo.alignmentRequirement = alignmentRequirement / CHAR_BIT; + + memberIndex++; + } else if (parentOffsetBits < memberOffsetBits) { + std::string typeName; + + if (typeToNameMap.find(parentClasses[e][parentIndex].type) != + typeToNameMap.end()) { + std::optional tmpStr = + getNameForType(parentClasses[e][parentIndex].type); + + if (!tmpStr.has_value()) { + return false; + } + + typeName = std::move(*tmpStr); + typeName.resize(15); + } + + VLOG(1) << "Add parent type: " << typeName << "(...) " + << parentClasses[e][parentIndex].type + << " currOffset: " << currOffsetBits + << " expectedOffset: " << parentOffsetBits; + + if (currOffsetBits > parentOffsetBits && parentOffsetBits != 0) { + // If a parent is empty, drgn can return parentOffsetBits as 0. So, + // unfortunately, this check needs to be loosened. + LOG(ERROR) << "Failed to match " << e << " currOffsetBits " + << currOffsetBits << " parentOffsetBits " + << parentOffsetBits; + return false; + } + + if (parentOffsetBits > currOffsetBits) { + addPadding(parentOffsetBits - currOffsetBits, code); + paddingInfo.paddingSize += parentOffsetBits - currOffsetBits; + paddingInfo.paddings.push_back(parentOffsetBits - currOffsetBits); + currOffsetBits = parentOffsetBits; + } + + auto parentTypeName = getNameForType(parentClasses[e][parentIndex].type); + if (!parentTypeName.has_value()) { + return false; + } + + VLOG(1) << "Generating parent " << *parentTypeName << " " + << parentClasses[e][parentIndex].type << " {"; + g_level += 1; + + size_t offsetToNextMember = 0; + if (parentIndex + 1 < parentClasses[e].size()) { + offsetToNextMember = parentClasses[e][parentIndex + 1].bit_offset; + } else { + if (memberOffsetBits != ULONG_MAX) { + offsetToNextMember = memberOffsetBits - currOffsetBits; + } else { // when members.size() == 0 + offsetToNextMember = 0; + } + } + + if (!generateParent(parentClasses[e][parentIndex].type, memberNames, + currOffsetBits, code, offsetToNextMember)) { + return false; + } + + auto currentMemberAlignmentRequirement = + getAlignmentRequirements(parentClasses[e][parentIndex].type); + + if (!currentMemberAlignmentRequirement.has_value()) { + return false; + } + + alignmentRequirement = + std::max(alignmentRequirement, *currentMemberAlignmentRequirement); + paddingInfo.alignmentRequirement = alignmentRequirement / CHAR_BIT; + + g_level -= 1; + VLOG(1) << "}"; + parentIndex++; + } else { + // if memberOffsetBits and parentOffsetBits are same, parent class must be + // empty + parentIndex++; + } + } while (true); + + auto szBytes = getDrgnTypeSize(e); + if (!szBytes.has_value()) { + return false; + } + + if (drgn_type_kind(e) != DRGN_TYPE_UNION && + // If class is empty, class size is at least one. But don't add the + // explicit padding. It can mess up with inheritance + (*szBytes > 1 || isNumMemberGreaterThanZero(e))) { + uint64_t szBits = (*szBytes * CHAR_BIT); + + if (offsetToNextMemberInSubclass && offsetToNextMemberInSubclass < szBits) { + /* We are laying out the members of a parent class*/ + szBits = offsetToNextMemberInSubclass; + } + + if (currOffsetBits < szBits) { + uint64_t paddingBits = szBits - currOffsetBits; + VLOG(1) << "Add padding to end of struct; curr " << currOffsetBits + << " sz " << szBits; + addPadding(paddingBits, code); + paddingInfo.paddingSize += paddingBits; + + if (paddingInfo.isSetSize == 0) { + /* + * We already account for the unnaligned remainder at the end of a + * struct if the struct has an Unpacked __is_set field. Example from + * PaddingHunter.h: + * if (isThriftStruct) { + * if (isSet_size) { + * saving_size = saving_from_packing(); + * odd_sum += isSet_offset - saving_from_packing(); + * } + * } + */ + paddingInfo.paddings.push_back(paddingBits); + } + } + currOffsetBits = szBits; + } else if (drgn_type_kind(e) == DRGN_TYPE_UNION) { + currOffsetBits = (*szBytes * CHAR_BIT); + } + + if (currOffsetBits % alignmentRequirement != 0) { + violatesAlignmentRequirement = true; + } + + VLOG(1) << "Returning " << e << " " << currOffsetBits; + + out_offset_bits = currOffsetBits; + return true; +} + +bool OICodeGen::generateStructDefs(std::string &code) { + std::vector structDefTypeCopy = structDefType; + std::map> parentClassesCopy = + parentClasses; + + while (!structDefTypeCopy.empty()) { + for (auto it = structDefTypeCopy.cbegin(); + it != structDefTypeCopy.cend();) { + struct drgn_type *e = *it; + if (classMembersMap.find(e) == classMembersMap.end()) { + LOG(ERROR) << "Failed to find in classMembersMap " << e; + return false; + } + + bool skip = false; + + // Make sure that all parent class types are defined + if (parentClassesCopy.find(e) != parentClassesCopy.end()) { + auto &parents = parentClassesCopy[e]; + for (auto &p : parents) { + auto it2 = std::find(structDefTypeCopy.begin(), + structDefTypeCopy.end(), p.type); + if (it2 != structDefTypeCopy.cend()) { + skip = true; + break; + } + } + } + + // Make sure that all member types are defined + if (!skip) { + auto &members = classMembersMap[e]; + for (auto &m : members) { + auto *underlyingType = drgnUnderlyingType(m.type); + + if (underlyingType != e) { + auto it2 = std::find(structDefTypeCopy.begin(), + structDefTypeCopy.end(), underlyingType); + if (it2 != structDefTypeCopy.cend()) { + skip = true; + break; + } + } + } + } + + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + return false; + } + + if (skip) { + ++it; + VLOG(1) << "Skipping " << *typeName << " " << e; + continue; + } else { + VLOG(1) << "Generating struct " << *typeName << " " << e << " {"; + g_level += 1; + + if (!generateStructDef(e, code)) { + return false; + } + + g_level -= 1; + VLOG(1) << "}"; + it = structDefTypeCopy.erase(it); + } + } + } + return true; +} + +bool OICodeGen::addStaticAssertsForType(struct drgn_type *type, + bool generateAssertsForOffsets, + std::string &code) { + auto struct_name = getNameForType(type); + if (!struct_name.has_value()) { + return false; + } + + auto sz = getDrgnTypeSize(type); + if (!sz.has_value()) { + return false; + } + + std::string assertStr = + "static_assert(sizeof(" + *struct_name + ") == " + std::to_string(*sz); + assertStr += ", \"Unexpected size of struct " + *struct_name + "\");\n"; + + std::unordered_map memberNames; + if (generateAssertsForOffsets && + !staticAssertMemberOffsets(*struct_name, type, assertStr, memberNames)) { + return false; + } + + code.append(assertStr); + return true; +} + +/* + * Generates a declaration for a given fully-qualified type. + * + * e.g. Given "nsA::nsB::Foo" + * + * The folowing is generated: + * namespace nsA { + * namespace nsB { + * struct Foo; + * } // nsB + * } // nsA + */ +void OICodeGen::declareThriftStruct(std::string &code, std::string_view name) { + if (auto pos = name.find("::"); pos != name.npos) { + auto ns = name.substr(0, pos); + code += "namespace "; + code += ns; + code += " {\n"; + // Recurse, continuing from after the "::" + declareThriftStruct(code, name.substr(pos + 2)); + code += "} // namespace "; + code += ns; + code += "\n"; + } else { + code += "struct "; + code += name; + code += ";\n"; + } +} + +bool OICodeGen::generateJitCode(std::string &code) { + // Include relevant headers + code.append("// relevant header includes -----\n"); + + // We always generate functions for `std::array` since we transform + // raw C arrays into equivalent instances of `std::array` + // This is kind of hacky and introduces a dependency on `std_array.toml` even + // when people may not expect it. + { + bool found = false; + for (const auto &el : containerInfoList) { + if (el->typeName == "std::array<") { + containerTypesFuncDef.insert(*el); + found = true; + break; + } + } + if (!found) { + LOG(ERROR) << "an implementation for `std::array` is required but none " + "was found"; + return false; + } + } + + std::set includedHeaders = config.defaultHeaders; + for (auto &e : containerTypesFuncDef) { + includedHeaders.insert(e.header); + } + + // these headers are included in OITraceCode.cpp + includedHeaders.erase("xmmintrin.h"); + includedHeaders.erase("utility"); + + // Required for the offsetof() macro + includedHeaders.insert("cstddef"); + + for (const auto &e : includedHeaders) { + code.append("#include <"); + code.append(e); + code.append(">\n"); + } + + code.append("// storage macro definitions -----\n"); + if (config.useDataSegment) { + code.append(R"( + #define SAVE_SIZE(val) + #define SAVE_DATA(val) StoreData(val, returnArg) + + #define JLOG(str) \ + do { \ + if (__builtin_expect(logFile, 0)) { \ + write(logFile, str, sizeof(str) - 1); \ + } \ + } while (false) + + #define JLOGPTR(ptr) \ + do { \ + if (__builtin_expect(logFile, 0)) { \ + __jlogptr((uintptr_t)ptr); \ + } \ + } while (false) + )"); + } else { + code.append(R"( + #define SAVE_SIZE(val) AddData(val, returnArg) + #define SAVE_DATA(val) + #define JLOG(str) + #define JLOGPTR(ptr) + )"); + } + + // The purpose of the anonymous namespace within `OIInternal` is that + // anything defined within an anonymous namespace has internal-linkage, + // and therefore won't appear in the symbol table of the resulting object + // file. Both OIL and OID do a linear search through the symbol table for + // the top-level `getSize` function to locate the probe entry point, so + // by keeping the contents of the symbol table to a minimum, we make that + // process faster. + std::string definitionsCode; + definitionsCode.append("namespace OIInternal {\nnamespace {\n"); + // Use relevant namespaces + definitionsCode.append("// namespace uses\n"); + + std::set usedNamespaces = config.defaultNamespaces; + for (const auto &v : containerTypesFuncDef) { + for (auto &e : v.ns) { + usedNamespaces.insert(std::string(e)); + } + } + + for (auto &e : usedNamespaces) { + definitionsCode.append("using "); + definitionsCode.append(e); + definitionsCode.append(";\n"); + } + + printAllTypeNames(); + + definitionsCode.append("// forward declarations -----\n"); + for (auto &e : structDefType) { + if (drgn_type_kind(e) == DRGN_TYPE_STRUCT || + drgn_type_kind(e) == DRGN_TYPE_CLASS) { + definitionsCode.append("struct"); + } else if (drgn_type_kind(e) == DRGN_TYPE_UNION) { + definitionsCode.append("union"); + } else { + LOG(ERROR) << "Failed to read type"; + return false; + } + + definitionsCode.append(" "); + if (typeToNameMap.find(e) == typeToNameMap.end()) { + LOG(ERROR) << "Failed to find " << e << " in typeToNameMap "; + return false; + } + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + return false; + } + + definitionsCode.append(typeName.value()); + definitionsCode.append(";\n"); + } + + definitionsCode.append("// stubbed classes -----\n"); + for (const auto &e : knownDummyTypeList) { + std::string name; + if (!getDrgnTypeName(e, name)) { + return false; + } + + auto typeName = getNameForType(e); + if (!typeName.has_value()) { + return false; + } + + uint64_t sz = 0; + if (auto *err = drgn_type_sizeof(e, &sz)) { + std::string knownTypeName; + if (auto opt = isTypeToStub(*typeName)) { + knownTypeName = opt.value(); + } + + LOG(ERROR) << "Failed to query size from drgn; look in sizeMap " + << knownTypeName << std::endl; + + if (auto it = sizeMap.find(knownTypeName); it != sizeMap.end()) { + sz = it->second; + } else { + LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message + << " " << e; + return false; + } + } + + auto alignment = getAlignmentRequirements(e).value_or(CHAR_BIT) / CHAR_BIT; + if (!isKnownType(name)) { + definitionsCode.append("struct alignas(" + std::to_string(alignment) + + ") " + (*typeName) + " { char dummy[" + + std::to_string(sz) + "];};\n"); + } else { + if (isTypeToStub(name)) { + definitionsCode.append("struct alignas(" + std::to_string(alignment) + + ") " + (*typeName) + " { char dummy[" + + std::to_string(sz) + "];};\n"); + } + } + } + + definitionsCode.append("// enums -----\n"); + for (auto &e : enumTypes) { + auto name = getNameForType(e); + + if (!name.has_value()) { + return false; + } + + uint64_t sz = 0; + auto *err = drgn_type_sizeof(e, &sz); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up size from drgn " << err->code << " " + << err->message << " "; + return false; + } + + switch (sz) { + case 8: + definitionsCode.append("typedef uint64_t " + *name + ";\n"); + break; + case 4: + definitionsCode.append("typedef uint32_t " + *name + ";\n"); + break; + case 2: + definitionsCode.append("typedef uint16_t " + *name + ";\n"); + break; + case 1: + definitionsCode.append("typedef uint8_t " + *name + ";\n"); + break; + default: + LOG(ERROR) << "Error invalid enum size " << *name << " " << sz; + return false; + } + } + + // This is basically a topological sort. We need to make sure typedefs + // are defined in the correct order. + auto sortedTypeDefMap = getSortedTypeDefMap(typedefTypes); + + definitionsCode.append("// typedefs -----\n"); + + // Typedefs + for (auto const &e : sortedTypeDefMap) { + auto tmpStr1 = getNameForType(e.first); + auto tmpStr2 = getNameForType(e.second); + + if (!tmpStr1.has_value() || !tmpStr2.has_value()) { + return false; + } + + definitionsCode.append("typedef " + *tmpStr2 + " " + *tmpStr1 + ";\n"); + } + + definitionsCode.append("// struct definitions -----\n"); + + if (!generateStructDefs(definitionsCode)) { + return false; + } + + funcGen.DeclareGetContainer(definitionsCode); + definitionsCode.append("}\n\n} // namespace OIInternal\n"); + + std::string functionsCode; + functionsCode.append("namespace OIInternal {\nnamespace {\n"); + functionsCode.append("// functions -----\n"); + if (!funcGen.DeclareGetSizeFuncs(functionsCode, containerTypesFuncDef, + config.chaseRawPointers)) { + LOG(ERROR) << "declaring get size for containers failed"; + return false; + } + + if (config.chaseRawPointers) { + functionsCode.append(R"( + template + void getSizeType(const T* t, size_t& returnArg); + + void getSizeType(const void *s_ptr, size_t& returnArg); + )"); + } + + for (auto &e : structDefType) { + if (drgn_type_kind(e) != DRGN_TYPE_UNION) { + auto typeName = getNameForType(e); + + if (!typeName.has_value()) { + return false; + } + + funcGen.DeclareGetSize(functionsCode, *typeName); + } + } + + if (config.useDataSegment || config.chaseRawPointers) { + funcGen.DeclareStoreData(functionsCode); + } + + if (config.useDataSegment) { + funcGen.DeclareEncodeData(functionsCode); + funcGen.DeclareEncodeDataSize(functionsCode); + } else { + funcGen.DeclareAddData(functionsCode); + } + + if (!funcGen.DefineGetSizeFuncs(functionsCode, containerTypesFuncDef, + config.chaseRawPointers)) { + LOG(ERROR) << "defining get size for containers failed"; + return false; + } + + if (config.chaseRawPointers) { + functionsCode.append(R"( + template + void getSizeType(const T* s_ptr, size_t& returnArg) + { + JLOG("ptr val @"); + JLOGPTR(s_ptr); + StoreData((uintptr_t)(s_ptr), returnArg); + if (s_ptr && pointers.add((uintptr_t)s_ptr)) { + getSizeType(*(s_ptr), returnArg); + } + } + + void getSizeType(const void *s_ptr, size_t& returnArg) + { + JLOG("void ptr @"); + JLOGPTR(s_ptr); + StoreData((uintptr_t)(s_ptr), returnArg); + } + )"); + } + + for (auto &e : structDefType) { + auto name = getNameForType(e); + + if (!name.has_value()) { + return false; + } + + if (name->find("TypedValue") == 0) { + funcGen.DefineGetSizeTypedValueFunc(functionsCode, *name); + } else if (drgn_type_kind(e) != DRGN_TYPE_UNION) { + getFuncDefinitionStr(functionsCode, e, *name); + } + } + + if (config.useDataSegment) { + funcGen.DefineStoreData(functionsCode); + funcGen.DefineEncodeData(functionsCode); + funcGen.DefineEncodeDataSize(functionsCode); + } else { + funcGen.DefineAddData(functionsCode); + } + + for (auto &structType : structDefType) { + // Don't generate member offset asserts for unions since we pad them out + bool generateOffsetAsserts = + (drgn_type_kind(structType) != DRGN_TYPE_UNION); + + if (!addStaticAssertsForType(structType, generateOffsetAsserts, + functionsCode)) { + return false; + } + } + + for (auto &container : containerTypeMapDrgn) { + // Don't generate member offset asserts since we don't unwind containers + if (!addStaticAssertsForType(container.first, false, functionsCode)) { + return false; + } + } + { + auto *type = rootTypeToIntrospect.type; + auto tmpStr = getNameForType(type); + + if (!tmpStr.has_value()) { + return false; + } + + std::string typeName = *tmpStr; + + // The top-level function needs to appear outside the `OIInternal` + // namespace, or otherwise strange relocation issues may occur. We create a + // `typedef` with a known name (`__ROOT_TYPE__`) *inside* the `OIInternal` + // namespace so that we can refer to it from the top-level function without + // having to worry about appropriately referring to types inside the + // `OIInternal` namespace from outside of it, which would involve dealing + // with all the complexities of prepending `OIInternal::` to not just the + // root type but all of its (possible) template parameters. + functionsCode.append("\ntypedef " + typeName + " __ROOT_TYPE__;\n"); + functionsCode.append("}\n\n} // namespace OIInternal\n"); + + /* Start function definitions. First define top level func for root object + */ + auto rawTypeName = typeToName(type); + if (config.useDataSegment) { + if (rootTypeStr.starts_with("unique_ptr") || + rootTypeStr.starts_with("LowPtr") || + rootTypeStr.starts_with("shared_ptr")) { + funcGen.DefineTopLevelGetSizeSmartPtr(functionsCode, rawTypeName); + } else { + funcGen.DefineTopLevelGetSizeRef(functionsCode, rawTypeName); + } + } else { + if (linkageName.empty()) { + funcGen.DefineTopLevelGetSizePtrRet(functionsCode, rawTypeName); + } else { + funcGen.DefineTopLevelGetObjectSize(functionsCode, rawTypeName, + linkageName); + } + } + } + + // Add definitions for Thrift data storage types. + // This is very brittle, but changes in the Thrift compiler should be caught + // by our integration tests. It might be better to build the definition of + // this struct from the debug info like we do for the types we're probing. + // That would require significant refactoring of CodeGen, however. + std::string thriftDefinitions; + for (const auto &t : thriftIssetStructTypes) { + auto fullyQualified = *fullyQualifiedName(t); + declareThriftStruct(thriftDefinitions, fullyQualified); + thriftDefinitions.append(R"( + namespace apache { namespace thrift { + template <> struct TStructDataStorage<)"); + thriftDefinitions.append(fullyQualified); + thriftDefinitions.append(R"(> { + static constexpr const std::size_t fields_size = 1; // Invalid, do not use + static const std::array fields_names; + static const std::array fields_ids; + static const std::array fields_types; + + static const std::array storage_names; + static const std::array __attribute__((weak)) isset_indexes; + }; + }} // namespace thrift, namespace apache + )"); + } + + code.append(definitionsCode); + code.append(thriftDefinitions); + code.append(functionsCode); + + std::ofstream ifs("/tmp/tmp_oid_output_2.cpp"); + ifs << code; + ifs.close(); + + return true; +} + +bool OICodeGen::isUnnamedStruct(struct drgn_type *type) { + return unnamedUnion.find(type) != unnamedUnion.end(); +} + +bool OICodeGen::generateNamesForTypes() { + std::map templateTransformMap; + + printAllTypes(); + + for (auto &e : structDefType) { + std::string name; + if (!getDrgnTypeName(e, name)) { + return false; + } + std::string tname = templateTransformType(name); + tname = structNameTransformType(tname); + + templateTransformMap[name] = tname; + addTypeToName(e, tname); + } + + // This sucks, make sure enumTypes are named before typedefTypes + // Otherwise there is a really confusing issue to untangle e.g. + // Assume groupa::groupb::OffendingType is an enum + // using OffendingType = groupa::groupb::OffendingType; + // + // This would result in 2 types a typedef and an enum. Make sure the + // underlying enum is named first. + for (auto &e : enumTypes) { + if (drgn_type_tag(e)) { + std::string name = drgn_type_tag(e); + addTypeToName(e, name); + } else { + static int index = 0; + std::string name = "__unnamed_enum_" + std::to_string(index); + addTypeToName(e, name); + } + } + + for (auto &e : typedefTypes) { + std::string name; + if (!getDrgnTypeName(e.first, name)) { + return false; + } + std::string tname = templateTransformType(name); + tname = structNameTransformType(tname); + addTypeToName(e.first, tname); + } + + VLOG(1) << "Printing size map"; + for (auto &e : sizeMap) { + VLOG(1) << "sizeMap[" << e.first << "] : " << e.second; + } + + for (auto &e : knownDummyTypeList) { + std::string name; + if (!getDrgnTypeName(e, name)) { + return false; + } + + std::string tname = templateTransformType(name); + VLOG(1) << "BOO type " << e << " " << name; + + if (tname.size() == 0) { + static int index = 0; + VLOG(1) << "Unnamed dummy struct"; + tname = "__unnamed_dummy_struct_" + std::to_string(index); + index++; + } + tname = structNameTransformType(tname); + templateTransformMap[name] = tname; + addTypeToName(e, tname); + } + + // funcDefTypeList should already include every type in classMembersMap? + + // This is rather unfortunate. Assume there is an type which is a pointer to + // another concrete type, e.g. struct Foo and Foo *. If Foo is renamed + // to Foo__0 because of a conflict, the pointer also needs to be renamed to + // Foo__0 *. In the first pass of funcDefTypeList and typedefTypes, assign + // names to all types except pointer types. Then in a second pass assign names + // to pointers (and also handle multiple levels of pointer). + + // TODO: Just create a separate list for pointer types. That would avoid + // having to iterate 2 times. + + // First pass, ignore pointer types + for (const auto &e : funcDefTypeList) { + if (isUnnamedStruct(e)) { + bool found = false; + for (auto &t : typedefTypes) { + if (t.second == e && drgn_type_kind(t.second) != DRGN_TYPE_ENUM) { + found = true; + break; + } + } + if (found) { + continue; + } + } else if (drgn_type_kind(e) == DRGN_TYPE_POINTER) { + continue; + } + + std::string name; + if (!getDrgnTypeName(e, name)) { + return false; + } + memberTransformName(templateTransformMap, name); + addTypeToName(e, name); + } + + // First pass, ignore pointer types + for (auto &e : typedefTypes) { + if (isUnnamedStruct(e.second) && + drgn_type_kind(e.second) != DRGN_TYPE_ENUM) { + } else { + // Apply template transform to only to the type which is already known + // i.e. for "typedef A B", only apply template transform to A + + if (drgn_type_kind(e.second) != DRGN_TYPE_POINTER) { + std::string name; + if (!getDrgnTypeName(e.second, name)) { + return false; + } + VLOG(2) << "Type2: " << e.second << " " << name; + memberTransformName(templateTransformMap, name); + VLOG(2) << "Type3: " << e.second << " " << name; + addTypeToName(e.second, name); + } + } + } + + // Second pass, name the pointer types + for (auto &e : funcDefTypeList) { + if (drgn_type_kind(e) == DRGN_TYPE_POINTER) { + int ptrDepth = 0; + struct drgn_type *ut = e; + while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) { + ut = drgn_type_type(ut).type; + ptrDepth++; + } + + auto ut_name = getNameForType(ut); + if (ut_name.has_value()) { + addTypeToName(e, (*ut_name) + std::string(ptrDepth, '*')); + } else { + std::string name; + if (!getDrgnTypeName(e, name)) { + return false; + } + memberTransformName(templateTransformMap, name); + addTypeToName(e, name); + } + } + } + + // Second pass, name the pointer types + for (auto &e : typedefTypes) { + if (drgn_type_kind(e.second) == DRGN_TYPE_POINTER) { + int ptrDepth = 0; + struct drgn_type *ut = e.second; + while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) { + ut = drgn_type_type(ut).type; + ptrDepth++; + } + + auto utName = getNameForType(ut); + if (utName.has_value()) { + addTypeToName(e.second, (*utName) + std::string(ptrDepth, '*')); + } else { + std::string name; + if (!getDrgnTypeName(e.second, name)) { + return false; + } + VLOG(2) << "Type2: " << e.second << " " << name; + memberTransformName(templateTransformMap, name); + VLOG(2) << "Type3: " << e.second << " " << name; + addTypeToName(e.second, name); + } + } + } + + { + // TODO: Do we need to apply a template transform to rootType + std::string name; + if (!getDrgnTypeName(rootType.type, name)) { + return false; + } + addTypeToName(rootType.type, name); + } + return true; +} + +bool OICodeGen::generate(std::string &code) { + if (!populateDefsAndDecls()) { + return false; + } + + if (!generateNamesForTypes()) { + return false; + } + + if (!generateJitCode(code)) { + return false; + } + + return true; +} + +std::optional OICodeGen::getRootType(SymbolService &symbols, + const irequest &req) { + if (req.type == "global") { + /* + * This is super simple as all we have to do is locate assign the + * type of the provided global variable. + */ + VLOG(1) << "Processing global: " << req.func; + + auto globalDesc = symbols.findGlobalDesc(req.func); + if (!globalDesc) { + return std::nullopt; + } + + auto *prog = symbols.getDrgnProgram(); + if (prog == nullptr) { + return std::nullopt; + } + + struct drgn_object global {}; + drgn_object_init(&global, prog); + if (auto *err = drgn_program_find_object(prog, req.func.c_str(), nullptr, + DRGN_FIND_OBJECT_ANY, &global)) { + LOG(ERROR) << "Failed to lookup global variable '" << req.func + << "': " << err->code << " " << err->message; + drgn_error_destroy(err); + return std::nullopt; + } + + return RootInfo{req.func, drgn_object_qualified_type(&global)}; + } + + VLOG(1) << "Passing : " << req.func; + auto fd = symbols.findFuncDesc(req); + if (!fd) { + VLOG(1) << "Failed to lookup function " << req.func; + return std::nullopt; + } + + // TODO: We are dealing with demangled names for drgn. drgn seems to store + // function names without parameters however. So strip parameters from + // demangled function name before passing to drgn. + // auto tmp = boost::core::demangle(req->func.c_str()); + // auto demangledName = tmp.substr(0, tmp.find("(")); + + auto *prog = symbols.getDrgnProgram(); + if (prog == nullptr) { + return std::nullopt; + } + + struct drgn_qualified_type ft {}; + if (auto *err = drgn_program_find_type_by_symbol_name( + prog, req.func.c_str(), &ft, nullptr, nullptr)) { + LOG(ERROR) << "Error when finding type by symbol " << err->code << " " + << err->message; + drgn_error_destroy(err); + return std::nullopt; + } + + if (req.isReturnRetVal()) { + VLOG(1) << "Processing return retval"; + return RootInfo{std::string("return"), drgn_type_type(ft.type)}; + } + + if (!drgn_type_has_parameters(ft.type)) { + LOG(ERROR) << "Error: Object is not function?"; + return std::nullopt; + } + + auto *params = drgn_type_parameters(ft.type); + auto paramsCount = drgn_type_num_parameters(ft.type); + if (paramsCount == 0) { + LOG(ERROR) << "Function " << req.func << " has no parameters"; + return std::nullopt; + } + + auto argIdxOpt = fd->getArgumentIndex(req.arg); + if (!argIdxOpt.has_value()) { + return std::nullopt; + } + + uint8_t argIdx = argIdxOpt.value(); + + /* + * The function descriptor has a fully populated argument vector so + * check that we have a valid argument decriptor for the requested arg. + * Most likely reason for it being invalid is if the DWARF formal parameter + * contains no location information. + */ + if (!fd->arguments[argIdx]->valid) { + LOG(ERROR) << "Argument " << argIdx << " for " << fd->symName + << " is invalid"; + return std::nullopt; + } + + struct drgn_qualified_type paramType {}; + auto *err = drgn_parameter_type(¶ms[argIdx], ¶mType); + if (err != nullptr) { + LOG(ERROR) << "Failed to get params: " << err->code << " " << err->message; + drgn_error_destroy(err); + return std::nullopt; + } + + std::string paramName; + if (params[argIdx].name) { + VLOG(1) << "ARG NAME: " << params[argIdx].name; + paramName = params[argIdx].name; + } + + return RootInfo{paramName, paramType}; +} + +/** + * Generate static_asserts for the offsets of each member of the given type + */ +bool OICodeGen::staticAssertMemberOffsets( + const std::string &struct_name, struct drgn_type *struct_type, + std::string &assert_str, std::unordered_map &memberNames, + uint64_t base_offset) { + if (knownDummyTypeList.find(struct_type) != knownDummyTypeList.end()) { + return true; + } + + if (drgn_type_kind(struct_type) == DRGN_TYPE_TYPEDEF) { + // Operate on the underlying type for typedefs + return staticAssertMemberOffsets(struct_name, + drgnUnderlyingType(struct_type), + assert_str, memberNames, base_offset); + } + + const auto *tag = drgn_type_tag(struct_type); + if (tag && isContainer(struct_type)) { + // We don't generate members for container types + return true; + } + + if (parentClasses.find(struct_type) != parentClasses.end()) { + // Recurse into parents to find inherited members + for (const auto &parent : parentClasses[struct_type]) { + auto parentOffset = base_offset + parent.bit_offset / CHAR_BIT; + if (!staticAssertMemberOffsets(struct_name, parent.type, assert_str, + memberNames, parentOffset)) { + return false; + } + } + } + + if (!drgn_type_has_members(struct_type)) { + return true; + } + + auto *members = drgn_type_members(struct_type); + for (size_t i = 0; i < drgn_type_num_members(struct_type); i++) { + if (members[i].name == nullptr) { + // Types can be defined in a class without assigning a member name e.g. + // struct A { struct {int i;} ;}; is a valid struct with size 4. + continue; + } + std::string memberName = members[i].name; + deduplicateMemberName(memberNames, memberName); + + std::replace(memberName.begin(), memberName.end(), '.', '_'); + + struct drgn_qualified_type memberQualType {}; + uint64_t bitFieldSize = 0; + auto *err = drgn_member_type(&members[i], &memberQualType, &bitFieldSize); + if (err != nullptr) { + LOG(ERROR) << "Error when looking up member type " << err->code << " " + << err->message << " " << memberName; + return false; + } + + // Can't use "offsetof" on bitfield members + if (bitFieldSize > 0) { + continue; + } + + auto expectedOffset = base_offset + members[i].bit_offset / CHAR_BIT; + assert_str += "static_assert(offsetof(" + struct_name + ", " + memberName + + ") == " + std::to_string(expectedOffset) + + ", \"Unexpected offset of " + struct_name + + "::" + memberName + "\");\n"; + } + + return true; +} + +std::map OICodeGen::getPaddingInfo() { + return paddedStructs; +} + +struct drgn_qualified_type OICodeGen::getRootType() { + return rootTypeToIntrospect; +}; + +void OICodeGen::setRootType(struct drgn_qualified_type rt) { + rootType = rt; +} + +TypeHierarchy OICodeGen::getTypeHierarchy() { + return { + .classMembersMap = getClassMembersMap(), + .containerTypeMap = containerTypeMapDrgn, + .typedefMap = typedefTypes, + .sizeMap = sizeMap, + .knownDummyTypeList = knownDummyTypeList, + .pointerToTypeMap = pointerToTypeMap, + .thriftIssetStructTypes = thriftIssetStructTypes, + }; +} + +std::string OICodeGen::Config::toString() const { + using namespace std::string_literals; + + // The list of ignored members must also be part of the remote hash + std::string ignoreMembers = "IgnoreMembers="; + for (const auto &ignore : membersToStub) { + ignoreMembers += ignore.first; + ignoreMembers += "::"; + ignoreMembers += ignore.second; + ignoreMembers += ';'; + } + + return (useDataSegment ? "" : "Dont") + "UseDataSegment,"s + + (chaseRawPointers ? "" : "Dont") + "ChaseRawPointers,"s + + (packStructs ? "" : "Dont") + "PackStructs,"s + + (genPaddingStats ? "" : "Dont") + "GenPaddingStats,"s + + (captureThriftIsset ? "" : "Dont") + "CaptureThriftIsset,"s + + ignoreMembers; +} + +std::vector OICodeGen::Config::toOptions() const { + return {"", // useDataSegment is always true? + (chaseRawPointers ? "-n" : ""), (packStructs ? "" : "-z"), + (genPaddingStats ? "" : "-w"), (captureThriftIsset ? "-T" : "")}; +} + +void OICodeGen::initializeCodeGen() { + LOG(WARNING) << "OICodeGen::initializeCodeGen is no longer necessary"; +}; diff --git a/src/OICodeGen.h b/src/OICodeGen.h new file mode 100644 index 0000000..f828450 --- /dev/null +++ b/src/OICodeGen.h @@ -0,0 +1,332 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +class SymbolService; +struct irequest; + +#include "Common.h" +#include "ContainerInfo.h" +#include "FuncGen.h" +#include "PaddingHunter.h" + +extern "C" { +#include +} + +namespace fs = std::filesystem; + +struct ParentMember { + struct drgn_type *type; + uint64_t bit_offset; + + bool operator<(const ParentMember &parent) const { + return (bit_offset < parent.bit_offset); + } +}; + +class OICodeGen { + public: + struct Config { + /* + * Note: don't set default values for the config so the user gets an + * uninitialized field" warning if they missed any. + */ + bool useDataSegment; + bool chaseRawPointers; + bool packStructs; + bool genPaddingStats; + bool captureThriftIsset; + + std::set containerConfigPaths{}; + std::set defaultHeaders{}; + std::set defaultNamespaces{}; + std::vector> membersToStub{}; + + std::string toString() const; + std::vector toOptions() const; + }; + + private: + // Private constructor. Please use the fallible `OICodeGen::buildFromConfig` + // for the expected behaviour. + OICodeGen(const Config &); + + public: + static std::unique_ptr buildFromConfig(const Config &); + bool generate(std::string &code); + + [[deprecated("Use generate(std::string&) instead.")]] bool + generateFunctionsForTypesDrgn(std::string &code) { + return generate(code); + } + + static std::optional getRootType(SymbolService &, const irequest &); + + bool registerContainer(const fs::path &); + + // TODO: remove me once all the callsites are gone + static void initializeCodeGen(); + + struct drgn_qualified_type getRootType(); + void setRootType(struct drgn_qualified_type rt); + void setLinkageName(std::string name) { + linkageName = name; + }; + TypeHierarchy getTypeHierarchy(); + std::map getPaddingInfo(); + + static void getDrgnArrayElementType(struct drgn_type *type, + struct drgn_type **outElemType, + size_t &outNumElems); + + bool isContainer(struct drgn_type *type); + + static struct drgn_type *drgnUnderlyingType(struct drgn_type *type); + + bool buildName(struct drgn_type *type, std::string &text, + std::string &outName); + + std::string typeToTransformedName(struct drgn_type *type); + static std::string typeToName(struct drgn_type *type); + + bool enumerateTypesRecurse(struct drgn_type *type); + static std::string_view drgnKindStr(struct drgn_type *type); + std::set processedTypes; + + private: + Config config{}; + FuncGen funcGen; + + using ContainerTypeMap = + std::pair>; + + using TemplateParamList = + std::vector>; + + using SortedTypeDefMap = + std::vector>; + + std::string rootTypeStr; + std::string linkageName; + std::map unnamedUnion; + std::map sizeMap; + std::map containerTypeMapDrgn; + std::vector> containerInfoList; + std::vector enumTypes; + std::vector typePath; + std::vector knownTypes; + struct drgn_qualified_type rootType; + struct drgn_qualified_type rootTypeToIntrospect; + + std::map typedefMap; + std::map> parentClasses; + + size_t pad_index = 0; + std::unordered_map> + paddingIndexMap; + + std::map typedefTypes; + std::map> + classMembersMap; + std::map> + classMembersMapCopy; + std::map typeToNameMap; + std::map nameToTypeMap; + std::set funcDefTypeList; + std::vector structDefType; + std::set knownDummyTypeList; + std::map pointerToTypeMap; + std::set thriftIssetStructTypes; + std::vector topoSortedStructTypes; + + std::set containerTypesFuncDef; + + std::map paddedStructs; + + std::map> + &getClassMembersMap(); + + class DrgnString { + struct FreeDeleter { + void operator()(void *allocated) { + free(allocated); + } + }; + + public: + std::string_view contents; + DrgnString(char *data, size_t length) + : contents{data, length}, _data{data} { + } + DrgnString() = delete; + + private: + std::unique_ptr _data; + }; + + static void prependQualifiers(enum drgn_qualifiers, std::string &sb); + static std::string stripFullyQualifiedName( + const std::string &fullyQualifiedName); + std::string stripFullyQualifiedNameWithSeparators( + const std::string &fullyQualifiedname); + static void removeTemplateParamAtIndex(std::vector ¶ms, + const size_t index); + std::unordered_map fullyQualifiedNames; + std::optional fullyQualifiedName( + struct drgn_type *type); + static SortedTypeDefMap getSortedTypeDefMap( + const std::map &typedefTypeMap); + + std::optional getContainerInfo(struct drgn_type *type); + void printAllTypes(); + void printAllTypeNames(); + void printTypePath(); + + static void addPaddingForBaseClass(struct drgn_type *type, + std::vector &def); + void addTypeToName(struct drgn_type *type, std::string name); + bool generateNamesForTypes(); + bool generateJitCode(std::string &code); + bool generateStructDefs(std::string &code); + bool generateStructDef(struct drgn_type *e, std::string &code); + bool getDrgnTypeName(struct drgn_type *type, std::string &outName); + + bool getDrgnTypeNameInt(struct drgn_type *type, std::string &outName); + bool populateDefsAndDecls(); + static void memberTransformName( + std::map &templateTransformMap, + std::string &typeName); + + bool getMemberDefinition(struct drgn_type *type); + bool isKnownType(const std::string &type); + bool isKnownType(const std::string &type, std::string &matched); + + static bool getTemplateParams( + struct drgn_type *type, size_t numTemplateParams, + std::vector> &v); + + bool enumerateTemplateParamIdxs(struct drgn_type *type, + const ContainerInfo &containerInfo, + const std::vector ¶mIdxs, + bool &ifStub); + bool getContainerTemplateParams(struct drgn_type *type, bool &ifStub); + void getFuncDefinitionStr(std::string &code, struct drgn_type *type, + const std::string &typeName); + std::optional getDrgnTypeSize(struct drgn_type *type); + + std::optional getNameForType(struct drgn_type *type); + + static std::string preProcessUniquePtr(struct drgn_type *type, + std::string name); + std::string transformTypeName(struct drgn_type *type, std::string &text); + static std::string templateTransformType(const std::string &typeName); + static std::string structNameTransformType(const std::string &typeName); + + bool addPadding(uint64_t padding_bits, std::string &code); + static void deduplicateMemberName( + std::unordered_map &memberNames, + std::string &memberName); + std::optional generateMember( + const DrgnClassMemberInfo &m, + std::unordered_map &memberNames, + uint64_t currOffsetBits, std::string &code, bool isInUnion); + bool generateParent(struct drgn_type *p, + std::unordered_map &memberNames, + uint64_t &currOffsetBits, std::string &code, + size_t offsetToNextMember); + std::optional getAlignmentRequirements(struct drgn_type *e); + bool generateStructMembers(struct drgn_type *e, + std::unordered_map &memberNames, + std::string &code, uint64_t &out_offset_bits, + PaddingInfo &paddingInfo, + bool &violatesAlignmentRequirement, + size_t offsetToNextMember); + void getFuncDefClassMembers(std::string &code, struct drgn_type *type, + std::unordered_map &memberNames, + bool skipPadding = false); + bool isDrgnSizeComplete(struct drgn_type *type); + + static bool getEnumUnderlyingTypeStr(struct drgn_type *e, + std::string &enumUnderlyingTypeStr); + + bool ifEnumerateClass(const std::string &typeName); + + bool enumerateClassParents(struct drgn_type *type, + const std::string &typeName); + bool enumerateClassMembers(struct drgn_type *type, + const std::string &typeName, bool &isStubbed); + bool enumerateClassTemplateParams(struct drgn_type *type, + const std::string &typeName, + bool &isStubbed); + bool ifGenerateMemberDefinition(const std::string &typeName); + bool generateMemberDefinition(struct drgn_type *type, std::string &typeName); + std::optional> isMemberToStub( + const std::string &type, const std::string &member); + std::optional isTypeToStub(const std::string &typeName); + bool isTypeToStub(struct drgn_type *type, const std::string &typeName); + bool isEmptyClassOrFunctionType(struct drgn_type *type, + const std::string &typeName); + bool enumerateClassType(struct drgn_type *type); + bool enumerateTypeDefType(struct drgn_type *type); + bool enumerateEnumType(struct drgn_type *type); + bool enumeratePointerType(struct drgn_type *type); + bool enumeratePrimitiveType(struct drgn_type *type); + bool enumerateArrayType(struct drgn_type *type); + + bool isUnnamedStruct(struct drgn_type *type); + + std::string getAnonName(struct drgn_type *, const char *); + std::string getStructName(struct drgn_type *type) { + return getAnonName(type, "__anon_struct_"); + } + std::string getUnionName(struct drgn_type *type) { + return getAnonName(type, "__anon_union_"); + } + static void declareThriftStruct(std::string &code, std::string_view name); + + bool isNumMemberGreaterThanZero(struct drgn_type *type); + void getClassMembersIncludingParent(struct drgn_type *type, + std::vector &out); + bool staticAssertMemberOffsets( + const std::string &struct_name, struct drgn_type *struct_type, + std::string &assert_str, + std::unordered_map &member_names, + uint64_t base_offset = 0); + bool addStaticAssertsForType(struct drgn_type *type, + bool generateAssertsForOffsets, + std::string &code); + bool buildNameInt(struct drgn_type *type, std::string &nameWithoutTemplate, + std::string &outName); + void replaceTemplateOperator( + std::vector> + &template_params, + std::vector &template_params_strings, size_t index); + void replaceTemplateParameters( + struct drgn_type *type, + std::vector> + &template_params, + std::vector &template_params_strings, + const std::string &nameWithoutTemplate); +}; diff --git a/src/OICompile.cpp b/src/OICompile.cpp new file mode 100644 index 0000000..613303e --- /dev/null +++ b/src/OICompile.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +extern "C" { +#include +} + +#include + +#include "OICodeGen.h" +#include "OICompiler.h" +#include "OILibraryImpl.h" +#include "OIUtils.h" +#include "ObjectIntrospection.h" + +namespace fs = std::filesystem; + +static void assert_drgn_succeeded(struct drgn_error *err, const char *message) { + if (err) { + LOG(ERROR) << message << ": " << err->message; + exit(EXIT_FAILURE); + } +} + +static bool compile_type( + struct drgn_qualified_type *type, uintptr_t function_addr, + OICompiler::Config &compilerConfig, OICodeGen::Config &generatorConfig, + std::vector> &results) { + std::string code = +#include "OITraceCode.cpp" + ; + + auto codegen = OICodeGen::buildFromConfig(generatorConfig); + if (!codegen) { + return false; + } + + codegen->setRootType(*type); + if (!codegen->generate(code)) { + return false; + } + + const auto identifier = + ObjectIntrospection::function_identifier(function_addr); + if (drgn_type_has_name(type->type)) { + VLOG(1) << "Identifier for type '" << drgn_type_name(type->type) << "' is " + << identifier; + } + OICompiler compiler{{}, compilerConfig}; + auto source_path = + identifier + + OICache::extensions[static_cast(OICache::Entity::Source)]; + auto object_path = + identifier + + OICache::extensions[static_cast(OICache::Entity::Object)]; + if (!compiler.compile(code, source_path, object_path)) { + return false; + } + results.emplace_back(identifier, object_path); + return true; +} + +int main(int argc, char *argv[]) { + google::LogToStderr(); + google::InitGoogleLogging("oi_compile"); + // Rudimentary argument parsing: + // argv[1] is the executable we want to perform codegen for + // argv[2] is an optional configuration file + if (argc <= 1) { + LOG(ERROR) + << "Please provide the file to perform codegen for as an argument"; + return EXIT_FAILURE; + } + if (argc > 3) { + LOG(ERROR) << "Unrecognized command line arguments, oi_compile accepts at " + "most two arguments (the file to perform codegen for, and " + "optionally a config file)"; + return EXIT_FAILURE; + } + const char *target = argv[1]; + if (!fs::exists(target)) { + LOG(ERROR) << target << ": file does not exist"; + return EXIT_FAILURE; + } + if (!fs::is_regular_file(target)) { + LOG(ERROR) << target << ": not a regular file"; + return EXIT_FAILURE; + } + const char *config_file = + argc == 3 ? argv[2] : "/usr/local/share/oi/base.oid.toml"; + if (!fs::exists(config_file)) { + LOG(ERROR) << config_file << ": file does not exist"; + return EXIT_FAILURE; + } + + // OI initialization + OICompiler::Config compilerConfig{}; + OICodeGen::Config generatorConfig{}; + if (!OIUtils::processConfigFile(config_file, compilerConfig, + generatorConfig)) { + LOG(ERROR) << "Failed to process config file"; + return EXIT_FAILURE; + } + generatorConfig.useDataSegment = false; + + // drgn initialization + char envVar[] = "RGN_ENABLE_TYPE_ITERATOR=1"; + if (putenv(envVar) == -1) { + PLOG(ERROR) + << "Failed to set environment variable DRGN_ENABLE_TYPE_ITERATOR"; + return EXIT_FAILURE; + } + struct drgn_program *prog; + assert_drgn_succeeded(drgn_program_create(NULL, &prog), + "Error while initializing drgn program"); + assert_drgn_succeeded( + drgn_program_load_debug_info(prog, &target, 1, false, false), + "Error while loading debug info"); + struct drgn_type_iterator *types; + assert_drgn_succeeded(drgn_oil_type_iterator_create(prog, &types), + "Error while creating type iterator"); + + // Perform codegen for each type + std::vector> results{}; + struct drgn_qualified_type *type; + uintptr_t *function_addrs; + std::size_t function_addrs_len; + assert_drgn_succeeded(drgn_oil_type_iterator_next( + types, &type, &function_addrs, &function_addrs_len), + "Error while advancing type iterator"); + while (type) { + for (std::size_t i = 0; i < function_addrs_len; i++) { + auto function_addr = function_addrs[i]; + + if (!compile_type(type, function_addr, compilerConfig, generatorConfig, + results)) { + if (drgn_type_has_name(type->type)) { + LOG(ERROR) << "Compilation failed for type '" + << drgn_type_name(type->type) << "'"; + } else { + LOG(ERROR) + << "Compilation failed for type with template function address 0x" + << std::hex << function_addr; + } + return EXIT_FAILURE; + } + } + free(function_addrs); + assert_drgn_succeeded( + drgn_oil_type_iterator_next(types, &type, &function_addrs, + &function_addrs_len), + "Error while advancing type iterator"); + } + drgn_type_iterator_destroy(types); + + // Finally, create a new executable by copying the given executable + // and appending the object files created by codegen to the new + // executable as ELF sections, with one section for each type. + std::string output = target + std::string("_oil"); + if (fs::exists(output)) { + LOG(WARNING) << output << " already exists, overwriting it"; + fs::remove(output); + } + fs::copy(target, output); + std::string command = "objcopy"; + for (auto &[identifier, object_path] : results) { + command += " --add-section "; + command += ObjectIntrospection::OI_SECTION_PREFIX; + command += identifier; + command += "="; + command += object_path; + } + command += " "; + command += output; + int status = system(command.c_str()); + + // Delete temporary files now that we're done with them + for (auto &[_, object_path] : results) { + fs::remove(object_path); + } + + if (status == -1) { + PLOG(ERROR) << "Failed to launch subprocess"; + return EXIT_FAILURE; + } else if (status != 0) { + LOG(ERROR) << "objcopy exited with non-zero exit code " << status; + return EXIT_FAILURE; + } +} diff --git a/src/OICompiler.cpp b/src/OICompiler.cpp new file mode 100644 index 0000000..9b605f3 --- /dev/null +++ b/src/OICompiler.cpp @@ -0,0 +1,638 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OICompiler.h" + +#ifndef OSS_ENABLE +#include "cea/object-introspection/internal/ManifoldCache.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Metrics.h" + +extern "C" { +#include +} + +using namespace std; +using namespace clang; +using namespace llvm; +using namespace llvm::object; +using namespace ObjectIntrospection; + +static const char *symbolLookupCallback( + [[maybe_unused]] void *disInfo, [[maybe_unused]] uint64_t referenceValue, + uint64_t *referenceType, [[maybe_unused]] uint64_t referencePC, + [[maybe_unused]] const char **referenceName) { + *referenceType = LLVMDisassembler_ReferenceType_InOut_None; + return nullptr; +} + +/* + * This structure's goal is to statically initialize parts of LLVM used by + * Disassembler. We're declaring a static global variable with a constructor + * doing the init calls once and for all, on our behalf. The destructor will + * then take care of the cleanup, at exit. + */ +static LLVMDisasmContextRef disassemblerContext = nullptr; +static struct LLVMInitializer { + LLVMInitializer() { + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetDisassembler(); + + disassemblerContext = LLVMCreateDisasm("x86_64-pc-linux", nullptr, 0, + nullptr, symbolLookupCallback); + if (!disassemblerContext) { + throw std::runtime_error("Failed to initialize disassemblerContext"); + } + + /* + * Enable Intel assembly syntax and print immediate values in hexadecimal. + * The order in which the options are set matters. Don't re-order! + */ + LLVMSetDisasmOptions(disassemblerContext, + LLVMDisassembler_Option_AsmPrinterVariant); + LLVMSetDisasmOptions(disassemblerContext, + LLVMDisassembler_Option_PrintImmHex); + } + + ~LLVMInitializer() { + LLVMDisasmDispose(disassemblerContext); + } +} llvmInitializer; + +std::optional +OICompiler::Disassembler::operator()() { + if (disassemblerContext == nullptr || std::empty(funcText)) { + return std::nullopt; + } + + size_t instSize = LLVMDisasmInstruction( + disassemblerContext, const_cast(std::data(funcText)), + std::size(funcText), 0, std::data(disassemblyBuffer), + std::size(disassemblyBuffer)); + if (instSize == 0) { + return std::nullopt; + } + + Instruction inst{ + offset, + {std::data(funcText), instSize}, + {std::data(disassemblyBuffer)}, + }; + + offset += instSize; + funcText.remove_prefix(instSize); + + return inst; +} + +/* + * Manage memory for the object files and handle symbol resolution. + * The interface is defined by LLVM and we setup our hooks to + * allocate memory and prepare the relocation. + */ +class OIMemoryManager : public RTDyldMemoryManager { + public: + struct Slab { + private: + /* + * Allocate a slab of memory out of which we will allocate text and data. + * One Slab correspond to one Object file. + * The slab is divided in two segments in this order: + * 1. The text segment, to host the executable instructions + * 2. The data segment, to host the static variables + * At the minute, we make no differentiation between RW/RO data segments. + * We don't set the correct permissions on the pages allocated in the target + * process. Adding that would require making lots of `mprotect(2)` syscalls + * and introduce more latency. + */ + static sys::MemoryBlock allocateBlock(size_t totalSize) { + std::error_code errorCode; + auto mem = sys::Memory::allocateMappedMemory( + alignTo(totalSize + 256, 256), // Extra to fit paddings added below + nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, errorCode); + + /* + * It looks like report_fatal_error() calls exit() by default. If it's + * not possible to allocate memory then we need to fail anyway but do it + * gracefully. Try installing an error handler and propogating the + * failure upwards so we can shutdown cleanly. + */ + if (errorCode) { + report_fatal_error("Can't allocate enough memory: " + + errorCode.message()); + } + + return mem; + } + + public: + Slab(size_t totalSize, size_t codeSize, size_t dataSize) + : memBlock{allocateBlock(totalSize)}, + /* + * Allow some extra space to allow for alignment needs of segments. + * 128 bytes should be ample and well within our "slop" allocation. + */ + textSegBase{(uintptr_t)memBlock.base()}, + textSegLimit{alignTo(textSegBase + codeSize, 128)}, + dataSegBase{textSegLimit}, + dataSegLimit{alignTo(dataSegBase + dataSize, 128)} { + assert(dataSegLimit <= + (uintptr_t)memBlock.base() + memBlock.allocatedSize()); + + /* Fill the slab with NOP instructions */ + memset(memBlock.base(), nopInst, memBlock.allocatedSize()); + } + + ~Slab() { + sys::Memory::releaseMappedMemory(memBlock); + } + + sys::MemoryBlock memBlock; + SmallVector functionSections{}; + SmallVector dataSections{}; + + uintptr_t textSegBase = 0; + const uintptr_t textSegLimit = 0; + + uintptr_t dataSegBase = 0; + const uintptr_t dataSegLimit = 0; + + uint8_t *allocate(uintptr_t Size, unsigned Alignment, bool isCode) { + auto *allocOffset = isCode ? &textSegBase : &dataSegBase; + auto allocLimit = isCode ? textSegLimit : dataSegLimit; + + VLOG(1) << "allocateFromSlab " << (isCode ? "Code " : "Data ") << " Size " + << Size << " allocOffset " << std::hex << *allocOffset + << " allocLimit " << allocLimit; + + auto allocAddr = alignTo(*allocOffset, Alignment); + auto newAllocOffset = allocAddr + Size; + if (newAllocOffset > allocLimit) { + LOG(ERROR) << "allocateFromSlab: " << (isCode ? "Code " : "Data ") + << "allocOffset= " << std::hex << *allocOffset + << " Size = " << Size << " allocLimit = " << allocLimit; + + /* See above comment about failure handling */ + report_fatal_error("Can't allocate enough memory from slab"); + } + + auto §ions = isCode ? functionSections : dataSections; + sections.emplace_back((void *)allocAddr, Size); + + *allocOffset = newAllocOffset; + + VLOG(1) << "allocateFromSlab return: " << std::hex << allocAddr; + return (uint8_t *)allocAddr; + } + }; + + SmallVector Slabs{}; + OIMemoryManager(std::shared_ptr ss, + const std::unordered_map &synths) + : RTDyldMemoryManager{}, + symbols{std::move(ss)}, + syntheticSymbols{synths} { + } + + /* Hook to make LLVM call `reserveAllocationSpace()` for each Object file */ + bool needsToReserveAllocationSpace(void) override { + return true; + } + void reserveAllocationSpace(uintptr_t, uint32_t, uintptr_t, uint32_t, + uintptr_t, uint32_t) override; + + uint8_t *allocateCodeSection(uintptr_t, unsigned, unsigned, + StringRef) override; + uint8_t *allocateDataSection(uintptr_t, unsigned, unsigned, StringRef, + bool) override; + + /* Hook to set up proper memory permission. We don't handle that */ + bool finalizeMemory(std::string *) override { + return false; + } + + /* Hook to locate symbols in the remote process */ + JITSymbol findSymbol(const std::string &) override; + + /* + * We don't use EH frames in this context, as we generate then copy to another + * process, and enabling them causes issues with folly crashing on oid exit. + */ + void registerEHFrames(uint8_t *, uint64_t, size_t) override { + } + void deregisterEHFrames() override { + } + + private: + std::shared_ptr symbols; + const std::unordered_map &syntheticSymbols; + + Slab ¤tSlab() { + assert(!Slabs.empty()); + return Slabs.back(); + } +}; + +void OIMemoryManager::reserveAllocationSpace( + uintptr_t codeSize, uint32_t codeAlign, uintptr_t roDataSize, + uint32_t roDataAlign, uintptr_t rwDataSize, uint32_t rwDataAlign) { + /* + * It looks like the sizes given to us already take into account the + * alignment restrictions the different type of sections may have. Aligning + * to the next 1KB boundary just for a bit of safety-slush (paranoia really). + */ + uint64_t totalSz = alignTo((codeSize + roDataSize + rwDataSize), 1024); + + VLOG(1) << "reserveAllocationSpace: codesize " << codeSize << " codeAlign " + << codeAlign << " roDataSize " << roDataSize << " roDataAlign " + << roDataAlign << " rwDataSize " << rwDataSize << " rwDataAlign " + << rwDataAlign << " (Total Size: " << totalSz << ")"; + + Slabs.emplace_back(totalSz, codeSize, roDataSize + rwDataSize + 128); + + const auto &currSlab = currentSlab(); + VLOG(1) << "reserveAllocationSpace: " << std::hex << "SlabBase " + << currSlab.memBlock.base() << " textSegBaseAlloc " + << currSlab.textSegBase << " textSegLimit " << currSlab.textSegLimit + << " dataSegBaseAlloc " << currSlab.dataSegBase << " dataSegLimit " + << currSlab.dataSegLimit; +} + +uint8_t *OIMemoryManager::allocateCodeSection( + uintptr_t size, unsigned alignment, [[maybe_unused]] unsigned sectionID, + StringRef sectionName) { + VLOG(1) << "allocateCodeSection(Size = " << size + << ", Alignment = " << alignment + << ", SectionName = " << sectionName.data() << ")"; + + return currentSlab().allocate(size, alignment, true /* isCode */); +} + +uint8_t *OIMemoryManager::allocateDataSection( + uintptr_t size, unsigned alignment, [[maybe_unused]] unsigned sectionID, + StringRef sectionName, [[maybe_unused]] bool isReadOnly) { + VLOG(1) << "allocateDataSection(Size = " << size + << ", Alignment = " << alignment + << ", SectionName = " << sectionName.data() << ")"; + + return currentSlab().allocate(size, alignment, false /* isCode */); +} + +/* + * This is called to locate external symbols when relocations are + * resolved. We have to lookup the symbol in the remote process every time, + * which sucks for performance. However, relocation can happen while the remote + * process is running, so this code is out of the hot path. + * We can't rely on LLVM to do this job because we are resolving symbols of a + * remote process. LLVM only handles resolving symbols for the current process. + */ +JITSymbol OIMemoryManager::findSymbol(const std::string &name) { + if (auto synth = syntheticSymbols.find(name); + synth != end(syntheticSymbols)) { + VLOG(1) << "findSymbol(" << name << ") = Synth " << std::hex + << synth->second; + return JITSymbol(synth->second, JITSymbolFlags::Exported); + } + + if (auto sym = symbols->locateSymbol(name)) { + VLOG(1) << "findSymbol(" << name << ") = " << std::hex << sym->addr; + return JITSymbol(sym->addr, JITSymbolFlags::Exported); + } + + if (name.compare(0, 37, "_ZN6apache6thrift18TStructDataStorage") == 0 && + name.compare(name.size() - 16, 16, "13isset_indexesE") == 0) { + /* + * Hack to make weak symbols work with MCJIT. + * + * MCJIT converts weak symbols into strong symbols, which means weak symbols + * we define in the JIT code will not be overridden by strong symbols in the + * remote process. + * + * Instead, if we want something to act as a weak symbol, we must not + * provide a definition at all. Then MCJIT will always search for it in the + * remote processes. + * - If a symbol is found in the remote process, it will be used as normal + * - If no symbol is found, we end up here. Return an address of "-1" to + * signal that the symbol was not resolved without raising an error. + * + * Before dereferencing the weak symbol in the JIT code, it should be + * compared against nullptr (not "-1"!). + * + * Note that __attribute__((weak)) is still required on the "weak" symbol's + * declaration. Otherwise the compiler may optimise away the null-checks. + */ + VLOG(1) << "findSymbol(" << name << ") = -1"; + return JITSymbol(-1, JITSymbolFlags::Exported); + } + + VLOG(1) << "findSymbol(" << name << ") = not found"; + return JITSymbol(nullptr); +} + +std::optional OICompiler::decodeInst( + const std::vector &funcText, uintptr_t offset) { + auto disassembler = Disassembler((const uint8_t *)funcText.data() + offset, + funcText.size() - offset); + + auto inst = disassembler(); + if (!inst) { + return std::nullopt; + } + + VLOG(1) << "Decoded instruction: " << inst->disassembly + << " size: " << inst->opcodes.size(); + return std::string(inst->disassembly); +} + +OICompiler::OICompiler(std::shared_ptr symbolService, Config cfg) + : symbols{std::move(symbolService)}, config{std::move(cfg)} { +} + +/* + * The constructor must be declared/defined, since the header uses forward + * declarations with std::unique_ptr. The compiler doesn't have all the + * information to generate the unique_ptr's destructor. So the destructor must + * be part of OICompiler.cpp, which have the complete type information for the + * forward declared classes. + */ +OICompiler::~OICompiler() = default; + +/* + * Disassembles the opcodes housed in the Slabs' code segments. + */ +static constexpr size_t kMaxInterFuncInstrPadding = 16; + +static void debugDisAsm( + const SmallVector &Slabs, + const OICompiler::RelocResult::RelocInfos &ObjectRelocInfos) { + VLOG(1) << "\nDisassembled Code"; + + /* Outer loop on each Object files that has been loaded */ + assert(Slabs.size() == ObjectRelocInfos.size()); + for (const auto &S : boost::combine(Slabs, ObjectRelocInfos)) { + const auto &[ObjFile, ObjRelInfo] = std::tie(S.get<0>(), S.get<1>()); + + /* Inner loop on each Function Section of a given Object file */ + for (const auto &textSec : ObjFile.functionSections) { + const auto offset = + (uintptr_t)textSec.base() - (uintptr_t)ObjFile.memBlock.base(); + const auto baseRelocAddress = ObjRelInfo.RelocAddr + offset; + + size_t instrCnt = 0; + size_t byteCnt = 0; + size_t consNop = 0; + auto dg = OICompiler::Disassembler((uint8_t *)textSec.base(), + textSec.allocatedSize()); + while (auto inst = dg()) { + instrCnt++; + byteCnt += inst->opcodes.size(); + + /* + * I currently don't know the size of the generated object code housed + * in the slab. I don't want to display all the 'nop' instructions at + * the end of that buffer but I do want to display the 'nops' that are + * padding in between the generated instructions. The following kinda + * sucks... + */ + if (inst->opcodes.size() == 1 && inst->opcodes[0] == nopInst) { + if (++consNop == kMaxInterFuncInstrPadding + 1) { + /* + * We're in the nop padding after all the generated instructions so + * stop. + */ + break; + } + } else { + consNop = 0; + } + + VLOG(1) << std::hex << inst->offset + baseRelocAddress << ": " + << inst->disassembly.data(); + } + + VLOG(1) << "Number of Instructions: " << instrCnt + << " Instruction bytes: " << byteCnt; + } + } +} + +bool OICompiler::compile(const std::string &code, const fs::path &sourcePath, + const fs::path &objectPath) { + Metrics::Tracing _("compile"); + + /* + * Note to whoever: if you're having problems compiling code, especially + * header issues, then make sure you thoroughly read the options list in + * include/clang/Basic/LangOptions.def. + */ + auto compInv = std::make_shared(); + + compInv->getLangOpts()->CPlusPlus = true; + compInv->getLangOpts()->CPlusPlus11 = true; + compInv->getLangOpts()->CPlusPlus14 = true; + compInv->getLangOpts()->CPlusPlus17 = true; + compInv->getLangOpts()->CPlusPlus20 = true; + // Required for various `__GCC_ATOMIC_*` macros to be defined + compInv->getLangOpts()->GNUCVersion = 11 * 100 * 100; // 11.0.0 + compInv->getLangOpts()->Bool = true; + compInv->getLangOpts()->WChar = true; + compInv->getLangOpts()->CXXOperatorNames = true; + compInv->getLangOpts()->DoubleSquareBracketAttributes = true; + compInv->getLangOpts()->ImplicitInt = false; + compInv->getLangOpts()->Exceptions = true; + compInv->getLangOpts()->CXXExceptions = true; + + compInv->getPreprocessorOpts(); + compInv->getPreprocessorOpts().addRemappedFile( + sourcePath.string(), MemoryBuffer::getMemBufferCopy(code).release()); + compInv->getPreprocessorOpts().UsePredefines = true; + + compInv->getFrontendOpts().Inputs.push_back( + FrontendInputFile(sourcePath.string(), InputKind{Language::CXX})); + compInv->getFrontendOpts().OutputFile = objectPath.string(); + compInv->getFrontendOpts().ProgramAction = clang::frontend::EmitObj; + + auto &headerSearchOptions = compInv->getHeaderSearchOpts(); + + for (const auto &path : config.userHeaderPaths) { + headerSearchOptions.AddPath( + path, clang::frontend::IncludeDirGroup::IndexHeaderMap, false, false); + } + + for (const auto &path : config.sysHeaderPaths) { + headerSearchOptions.AddPath(path, clang::frontend::IncludeDirGroup::System, + false, false); + } + + compInv->getFrontendOpts().OutputFile = objectPath; + compInv->getTargetOpts().Triple = + llvm::Triple::normalize(llvm::sys::getProcessTriple()); + compInv->getCodeGenOpts().RelocationModel = llvm::Reloc::Static; + compInv->getCodeGenOpts().CodeModel = "large"; + compInv->getCodeGenOpts().OptimizationLevel = 3; + compInv->getCodeGenOpts().NoUseJumpTables = 1; + + if (config.generateJitDebugInfo) { + compInv->getCodeGenOpts().setDebugInfo(codegenoptions::FullDebugInfo); + } + + CompilerInstance compInstance; + compInstance.setInvocation(compInv); + compInstance.createDiagnostics(); + EmitObjAction compilerAction; + + bool execute = compInstance.ExecuteAction(compilerAction); + + if (!execute) { + LOG(ERROR) << "Execute failed"; + return false; + } + + /* LLVM 12 seems to be unable to handle the large files we create, + and consistently dies with the message: + 'fatal error: sorry, this include generates a translation unit too large + for Clang to process.' + So this is disabled for now. + if (VLOG_IS_ON(2)) { + // TODO: Maybe accept file path as an arg to dump the preprocessed file. + // Dumping to /tmp seems to require root permission + if (access("oi_preprocessed", F_OK) == 0 && + access("oi_preprocessed", R_OK | W_OK) != 0) { + LOG(ERROR) << "Trying to write oi_preprocessed, " + << "but it cannot be overwritten. Either remove it or run " + "oid with root priviledges "; + } else { + compInv->getFrontendOpts().OutputFile = "oi_preprocessed"; + compInv->getLangOpts()->LineComment = 1; + compInv->getPreprocessorOutputOpts().ShowCPP = 1; + auto act = new PrintPreprocessedAction(); + CI.ExecuteAction(*act); + VLOG(1) << "Dumped preprocessed output to file: " + << compInv->getFrontendOpts().OutputFile; + } + } + */ + + return true; +} + +std::optional OICompiler::applyRelocs( + uintptr_t baseRelocAddress, const std::set &objectFiles, + const std::unordered_map &syntheticSymbols) { + Metrics::Tracing relocationTracing("relocation"); + + memMgr = std::make_unique(symbols, syntheticSymbols); + RuntimeDyld dyld(*memMgr, *memMgr); + + /* Load all the object files into the MemoryManager */ + for (const auto &objPath : objectFiles) { + VLOG(1) << "Loading object file " << objPath; + auto objFile = ObjectFile::createObjectFile(objPath.c_str()); + if (!objFile) { + raw_os_ostream(LOG(ERROR)) << "Failed to load object file " << objPath + << ": " << objFile.takeError(); + return std::nullopt; + } + + dyld.loadObject(*objFile->getBinary()); + if (dyld.hasError()) { + LOG(ERROR) << "load object failed: " << dyld.getErrorString().data(); + return std::nullopt; + } + } + + RelocResult res; + res.relocInfos.reserve(memMgr->Slabs.size()); + + /* Provides mapping addresses to the MemoryManager */ + uintptr_t currentRelocAddress = baseRelocAddress; + for (const auto &slab : memMgr->Slabs) { + for (const auto &funcSection : slab.functionSections) { + auto offset = + (uintptr_t)funcSection.base() - (uintptr_t)slab.memBlock.base(); + dyld.mapSectionAddress(funcSection.base(), currentRelocAddress + offset); + + VLOG(1) << std::hex << "Relocated code " << funcSection.base() << " to " + << currentRelocAddress + offset; + } + + for (const auto &dataSection : slab.dataSections) { + auto offset = + (uintptr_t)dataSection.base() - (uintptr_t)slab.memBlock.base(); + dyld.mapSectionAddress(dataSection.base(), currentRelocAddress + offset); + + VLOG(1) << std::hex << "Relocated data " << dataSection.base() << " to " + << currentRelocAddress + offset; + } + + res.relocInfos.push_back(RelocResult::RelocInfo{ + (uintptr_t)slab.memBlock.base(), currentRelocAddress, + slab.memBlock.allocatedSize()}); + currentRelocAddress = + alignTo(currentRelocAddress + slab.memBlock.allocatedSize(), 128); + res.newBaseRelocAddr = currentRelocAddress; + } + + /* Apply relocation, record EH, etc. */ + dyld.finalizeWithMemoryManagerLocking(); + + if (dyld.hasError()) { + LOG(ERROR) << "relocation finalization failed: " + << dyld.getErrorString().str(); + return std::nullopt; + } + + /* Copy symbol table into `res` */ + auto symbolTable = dyld.getSymbolTable(); + res.symbols.reserve(symbolTable.size()); + for (const auto &[symName, sym] : symbolTable) { + res.symbols.emplace(symName.str(), sym.getAddress()); + } + + relocationTracing.stop(); + + if (VLOG_IS_ON(3)) { + debugDisAsm(memMgr->Slabs, res.relocInfos); + } + + return res; +} diff --git a/src/OICompiler.h b/src/OICompiler.h new file mode 100644 index 0000000..35fff86 --- /dev/null +++ b/src/OICompiler.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SymbolService.h" +#include "X86InstDefs.h" + +namespace fs = std::filesystem; + +class OIMemoryManager; + +/** + * `OICompiler` provides the tools to compile and relocate code. + * It also provides utilities to manipulate X86 assembly instructions + * and retrieve the type to introspect for a given `irequest`. + */ +class OICompiler { + public: + /* Configuration option for `OICompiler` */ + struct Config { + /* Whether to generate DWARF debug info for the JIT code */ + bool generateJitDebugInfo = false; + + std::vector userHeaderPaths{}; + std::vector sysHeaderPaths{}; + }; + + /** + * The result of a call to `applyRelocs()`. + * It contains the next BaseRelocAddress for further relocation, + * information about the Objects' buffers location and symbols. + */ + struct RelocResult { + /** + * Information about an Object file and its relocation. + * + * BaseAddr contains the address of the object file buffer. + * RelocAddr contains the relocated address of the object file buffer. + * Size is the size of the object file buffer in bytes. + * + * You typically need to copy the BaseAddr buffer to RelocAddr in order to + * finalize the relocation. + * Note that we don't manage separate segments for code and data. Thus, the + * RelocInfo struct describes one Slab which contains the two segments. + */ + struct RelocInfo { + uintptr_t BaseAddr, RelocAddr; + size_t Size; + }; + + using RelocInfos = std::vector; + using SymTable = std::unordered_map; + + uintptr_t newBaseRelocAddr; + RelocInfos relocInfos; + SymTable symbols; + }; + + /** + * Generator that takes a series of opcodes in + * and output the corresponding disassembled instructions. + */ + class Disassembler { + public: + /* + * Please forgive me :( + * We have to remain compatible with C++17 for OICompiler.cpp + * So we use std::basic_string_view instead of std::span. + */ + template + using Span = std::basic_string_view; + + /** + * Instruction holds the information returned by the disassembler. + * The fields are valid until a new Instruction struct has been + * output by the disassembler. + * There is no ownership on the fields, so copy the values + * in owning data-structure, if you want to extend the lifetime. + */ + struct Instruction { + uintptr_t offset; + Span opcodes; + std::string_view disassembly; + }; + + /** + * Create a disassembler from anything that resemble a std::span. + */ + template + Disassembler(Args &&...args) : funcText(std::forward(args)...) { + } + + /* + * Disassemble the next instuction, if any, and return the + * corresponding Instruction struct. + */ + std::optional operator()(); + + private: + uintptr_t offset = 0; + Span funcText; + std::array disassemblyBuffer; + }; + + OICompiler(std::shared_ptr, Config); + ~OICompiler(); + + /** + * Compile the given @param code and write the result in @param objectPath. + * + * @param code the C++ source code to compile + * @param sourcePath path/name of the code to compile (not used) + * @param objectPath path where to write the resulting Object file + * + * @return true if the compilation succeeded, false otherwise. + */ + bool compile(const std::string &, const fs::path &, const fs::path &); + + /** + * Load the @param objectFiles in memory and apply relocation at + * @param BaseRelocAddress. Note that it doesn't copy the object files at the + * @param BaseRelocAddress, but returns all the information to make the copy. + * Note: synthetic variables are symbols we define, but are used within the + * JIT code. See 'OITraceCode.cpp' and its global variables declared with + * extern. + * + * @param BaseRelocAddress where will the relocated code be located + * @param objectFiles paths to the object files to load and relocate + * @param syntheticSymbols a symbol table for synthetic variables + * + * @return a `std::optional` containing @ref RelocResult if the relocation was + * successful. Calling `applyRelocs()` again invalidates the Segments + * information. So make sure you copy the Segments' content before doing + * another call. + */ + std::optional applyRelocs( + uintptr_t, const std::set &, + const std::unordered_map &); + + /** + * Locates all the offsets of the given @param insts opcodes + * in the @param funcText. Typically used to find all `ret` instructions + * within a function. + * + * @param funcText the binary instruction of a function + * @param insts an array of instruction buffers to look for in @param funcText + * + * @return an optional with the offsets where the given instructions were + * found + */ + template + static std::optional> locateOpcodes( + const FuncTextRange &funcText, const NeedlesRange &needles); + + /** + * @return a string representation of the opcode(s) of the instruction found + * at @param offset within function's binary instructions @param funcText. + */ + static std::optional decodeInst(const std::vector &, + uintptr_t); + + private: + std::shared_ptr symbols; + Config config; + + /** + * memMgr is only used by applyReloc, but its lifetime must be larger than + * the duration of the function. The RelocResult returned references addrs + * manager by the Memory Manager, so we need to let the caller copy the code + * and data sections to their final location before release the Objects + * memory. + * This is why memMgr is a std::unique_ptr in the class instead of a local + * variable in the applyReloc function. + */ + std::unique_ptr memMgr; +}; + +template +std::optional> OICompiler::locateOpcodes( + const FuncTextRange &funcText, const NeedlesRange &needles) { + auto DG = Disassembler((uint8_t *)std::data(funcText), std::size(funcText)); + + std::vector locs; + while (auto inst = DG()) { + auto it = std::find_if( + std::begin(needles), std::end(needles), [&](const auto &needle) { + // Inst->opcodes.starts_with(needle); + return 0 == + inst->opcodes.find(OICompiler::Disassembler::Span( + std::data(needle), std::size(needle))); + }); + + if (it != std::end(needles)) { + locs.push_back(inst->offset); + } + } + + return locs; +} diff --git a/src/OID.cpp b/src/OID.cpp new file mode 100644 index 0000000..7da8d41 --- /dev/null +++ b/src/OID.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#include "Metrics.h" +#include "OIDebugger.h" +#include "OIOpts.h" +#include "PaddingHunter.h" +#include "TimeUtils.h" +#include "TreeBuilder.h" + +/* Global for signal handling */ +std::weak_ptr weak_oid; + +namespace fs = std::filesystem; +using namespace ObjectIntrospection; + +// Using an enum inside a namespace here instead of an `enum class` because +// enums defined via `enum class` aren't implicitly convertible to `int`, and +// having to cast the argument for each call to `exit` would be ugly. +namespace ExitStatus { +enum ExitStatus { + Success = EXIT_SUCCESS, + UsageError, + FileNotFoundError, + ConfigGenerationError, + ScriptParsingError, + StopTargetError, + SegmentRemovalError, + SegmentInitError, + CompilationError, + PatchingError, + ProcessingTargetDataError, + OidObjectError, + CacheUploadError, +}; +} + +/* + * This is the main driver code for the Object Introspection (OI) debugger. + * The 'oid' debugger is the driver application which instruments a target + * application to collect data and then reaps that data from the target. + * + * The flow of work in 'oid' can, roughly speaking, be split into several + * phases: + * + * Phase 1 - Object Discovery + * Using the 'drgn' debugger library, discover the container types in a + * given parent object and its descendent objects. With this information we + * can locate the addresses in memory of these container objects. + * + * Phase 2 - Code Generation + * Auto generate C++ code to iterate over the data structures of interest, + * calculate the size of these objects and record the data. + * + * Phase 3 - Object Code Generation + * JIT compile the C++ code into object code and relocate the resulting + * text into the traget processes address space. This is done using + * clang/llvm APIs. + * + * Phase 4 - Target Process Instrumentation + * The generated object code is injected into the target process in a text + * segment created apriori. Threads are captured and controlled at probe + * sites using breakpoint traps and the ptrace(2) interfaces. + * + * Phase 5 - Data processing + * The results are retrieved from the target processes data buffer and + * processed. The data buffer is a data segment that we mapped into the + * target process. + * + * In addition to the above phases we have process control which is + * currently based around ptrace(2). + */ + +constexpr static OIOpts opts{ + OIOpt{'h', "help", no_argument, nullptr, "Print this message and exit"}, + OIOpt{'p', "pid", required_argument, "", + "Target process to attach to"}, + OIOpt{'c', "config-file", required_argument, nullptr, + ""}, + OIOpt{'x', "data-buf-size", required_argument, "", + "Size of data segment (default:1MB)\n" + "Accepts multiplicative suffix: K, M, G, T, P, E"}, + OIOpt{'d', "debug-level", required_argument, "", + "Verbose level for logging"}, + OIOpt{'l', "jit-logging", no_argument, nullptr, "Enable JIT's logs"}, + OIOpt{'r', "remove-mappings", no_argument, nullptr, + "Remove oid mappings from target process"}, + OIOpt{'s', "script", required_argument, nullptr, ""}, + OIOpt{'S', "script-source", required_argument, nullptr, "type:symbol:arg"}, + OIOpt{'t', "timeout", required_argument, "", + "How long to probe the target process for"}, + OIOpt{'k', "custom-code-file", required_argument, nullptr, + "\n" + "Use your own CPP file instead of CodeGen"}, + OIOpt{'e', "compile-and-exit", no_argument, nullptr, + "Compile only then exit"}, + OIOpt{'o', "cache-path", required_argument, "", + "Enable caching using the provided directory"}, + OIOpt{'u', "cache-remote", required_argument, nullptr, + "Enable upload/download of cache files\n" + "Pick from {both,upload,download}"}, + OIOpt{'i', "debug-path", required_argument, nullptr, + "\n" + "Run oid on a executable with debug infos instead of a running " + "process"}, + // Optional arguments are pretty nasty - it will only work as + // "--dump-json=PATH" and not "--dump-json PATH". Try and make this take a + // required argument at a later point + OIOpt{'J', "dump-json", optional_argument, "[oid_out.json]", + "File to dump the results to, as JSON\n" + "(in addition to the default RocksDB output)"}, + OIOpt{ + 'B', "dump-data-segment", no_argument, nullptr, + "Dump the data segment's content, before TreeBuilder processes it\n" + "Each argument gets its own dump file: 'dataseg...dump'"}, + OIOpt{'j', "generate-jit-debug", no_argument, nullptr, + "Output debug info for the generated JIT code"}, + OIOpt{'n', "chase-raw-pointers", no_argument, nullptr, + "Generate probe for raw pointers"}, + OIOpt{'a', "log-all-structs", no_argument, nullptr, "Log all structures"}, + OIOpt{'z', "disable-packed-structs", no_argument, nullptr, + "Disable appending packed attributes to the definition of structs"}, + OIOpt{'w', "disable-padding-hunter", no_argument, nullptr, + "Disable Padding Hunter\n" + "Padded structs will be written to file called PADDING"}, + OIOpt{'T', "capture-thrift-isset", no_argument, nullptr, + "Capture the isset value for Thrift fields"}, + OIOpt{'m', "mode", required_argument, "[prod]", + "Allows to specify a mode of operation/group of settings"}, +}; + +void usage() { + std::cout << "usage: oid ...\n"; + std::cout << opts; + + std::cout << "\n\tFor problem reporting, questions and general comments " + "please pop along" + "\n\tto the Object Introspection Workplace group at " + "https://fburl.com/oid.\n" + << std::endl; +} + +/* + * This handler currently isn't completely async-signal-safe. It's mostly + * all in the segment removal code and is commented in appropriate places. + * The error messages are obviously not safe either. + */ +void sigIntHandler(int sigNum) { + VLOG(1) << "Received SIGNAL " << sigNum; + + if (auto oid = weak_oid.lock()) { + oid->stopAll(); + } else { + /* + * A small window exists between install a handler and creating the main + * debugger object. + */ + LOG(ERROR) << "Failed to find oid object when handling signal"; + exit(ExitStatus::OidObjectError); + } +} + +void installSigHandlers(void) { + struct sigaction nact {}; + struct sigaction oact {}; + + nact.sa_handler = sigIntHandler; + sigemptyset(&nact.sa_mask); + nact.sa_flags = SA_SIGINFO; + + sigaction(SIGINT, nullptr, &oact); + if (oact.sa_handler != SIG_IGN) { + sigaction(SIGINT, &nact, nullptr); + } + + /* Also stop on SIGALRM, for handling timeout */ + sigaction(SIGALRM, &nact, nullptr); +} + +std::optional strunittol(const char *str) { + errno = 0; + char *strend = nullptr; + long retval = strtol(str, &strend, 10); + if (errno != 0) { + return std::nullopt; + } + switch (*strend) { + case 'E': + retval *= 1024; + [[fallthrough]]; + case 'P': + retval *= 1024; + [[fallthrough]]; + case 'T': + retval *= 1024; + [[fallthrough]]; + case 'G': + retval *= 1024; + [[fallthrough]]; + case 'M': + retval *= 1024; + [[fallthrough]]; + case 'K': + retval *= 1024; + if (*(strend + 1) != '\0') { + return std::nullopt; + } + [[fallthrough]]; + case '\0': + break; + default: + return std::nullopt; + } + return retval; +} + +namespace Oid { + +struct Config { + pid_t pid; + std::string debugInfoFile; + std::string configFile; + fs::path cacheBasePath; + fs::path customCodeFile; + size_t dataSegSize; + int timeout_s; + bool cacheRemoteUpload; + bool cacheRemoteDownload; + bool enableJitLogging; + bool removeMappings; + bool generateJitDebug; + bool compAndExit; + bool genPaddingStats = true; + bool attachToProcess = true; + bool hardDisableDrgn = false; +}; + +} // namespace Oid + +static ExitStatus::ExitStatus runScript(const std::string &fileName, + std::istream &script, + const Oid::Config &oidConfig, + const OICodeGen::Config &codeGenConfig, + const TreeBuilder::Config &tbConfig) { + if (!fileName.empty()) { + VLOG(1) << "SCR FILE: " << fileName; + } + + auto progStart = time_hr::now(); + + std::shared_ptr oid; // share oid with the global signal handler + if (oidConfig.pid != 0) { + oid = std::make_shared(oidConfig.pid, oidConfig.configFile, + codeGenConfig, tbConfig); + } else { + oid = std::make_shared( + oidConfig.debugInfoFile, oidConfig.configFile, codeGenConfig, tbConfig); + } + weak_oid = oid; // set the weak_ptr for signal handlers + + if (!oidConfig.cacheBasePath.empty()) { + oid->setCacheBasePath(oidConfig.cacheBasePath); + } + + oid->setCacheRemoteEnabled(oidConfig.cacheRemoteUpload, + oidConfig.cacheRemoteDownload); + oid->setCustomCodeFile(oidConfig.customCodeFile); + oid->setEnableJitLogging(oidConfig.enableJitLogging); + oid->setGenerateJitDebugInfo(oidConfig.generateJitDebug); + oid->setHardDisableDrgn(oidConfig.hardDisableDrgn); + + VLOG(1) << "OIDebugger constructor took " << std::dec + << time_ns(time_hr::now() - progStart) << " nsecs"; + + LOG(INFO) << "Script file: " << fileName; + + if (!oid->parseScript(script)) { + LOG(ERROR) << "Error parsing input file '" << fileName << "'"; + return ExitStatus::ScriptParsingError; + } + + if (oidConfig.attachToProcess && !oid->stopTarget()) { + LOG(ERROR) << "Couldn't stop target process with PID " << oidConfig.pid; + return ExitStatus::StopTargetError; + } + auto initStart = time_hr::now(); + + /* + * Remove any existing mappings if the '-r' flag is used or if any of the + * segments have been explicitly changed on the command line. It's a bit of + * a heavy hammer to remove both text and data if only one of the relevant + * parameters have been set but that can always be modified in the future + * if necessary. + */ + if (oidConfig.attachToProcess) { + if (oidConfig.removeMappings) { + if (!oid->segConfigExists()) { + LOG(INFO) << "No config exists for pid " << oidConfig.pid + << " : cannot remove mappings"; + } else if (!oid->unmapSegments(true)) { + LOG(ERROR) << "Failed to remove segments in target process with PID " + << oidConfig.pid; + return ExitStatus::SegmentRemovalError; + } + + return ExitStatus::Success; + } + + if (oidConfig.dataSegSize > 0) { + oid->setDataSegmentSize(oidConfig.dataSegSize); + } + + if (!oid->segmentInit()) { + oid->contTargetThread(); + LOG(ERROR) << "Failed to initialise segments in target process with PID " + << oidConfig.pid; + return ExitStatus::SegmentInitError; + } + + // continue and detach main thread + oid->contTargetThread(); + } + + VLOG(1) << "init took " << std::dec << time_ns(time_hr::now() - initStart) + << " nsecs\n" + << "Compilation Started"; + + auto compileStart = time_hr::now(); + + if (!oid->compileCode()) { + LOG(ERROR) << "Compilation failed"; + return ExitStatus::CompilationError; + } + + VLOG(1) << "Compilation Finished (" << std::dec + << time_ns(time_hr::now() - compileStart) << " nsecs)"; + + if (oidConfig.compAndExit) { + // Ensure the .th cache file also gets created + oid->getTreeBuilderTyping(); + + if (oidConfig.genPaddingStats) { + PaddingHunter paddingHunter; + paddingHunter.localPaddedStructs = oid->getPaddingInfo(); + paddingHunter.processLocalPaddingInfo(); + paddingHunter.outputPaddingInfo(); + } + } else { + installSigHandlers(); + + /* + * Sigh. This is nonsense really and is tied to a single probe enabling. + * This will need re-architecting when we move to multiple enablings. + */ + if (!oid->isGlobalDataProbeEnabled()) { + oid->setMode(OIDebugger::OID_MODE_FUNC); + } + + /* + * I think we might be able to just fit the global variable work entirely + * under patchFunctions and therefore leave the shape of the code at + * this level pretty much unaltered. + */ + if (!oid->stopTarget()) { + LOG(ERROR) << "Couldn't stop target process with PID " << oidConfig.pid; + return ExitStatus::StopTargetError; + } + + if (!oid->patchFunctions()) { + oid->contTargetThread(); + LOG(ERROR) << "Error patching functions"; + return ExitStatus::PatchingError; + } + + oid->contTargetThread(false); + + if (oidConfig.timeout_s > 0) { + alarm(oidConfig.timeout_s); + } + + while (!oid->isInterrupted()) { + if (oid->processTrap(oidConfig.pid) == OIDebugger::OID_DONE) { + break; + } + }; + + // Disable timeout timer + alarm(0); + + // Cleanup all the remaining traps that were injected + if (!oid->removeTraps(0)) { + LOG(ERROR) << "Failed to remove instrumentation..."; + } + + { // Resume stopped thread before cleanup + VLOG(1) << "Resuming stopped threads..."; + Metrics::Tracing __("resume_threads"); + while (oid->processTrap(oidConfig.pid, false) == OIDebugger::OID_CONT) { + } + } + + oid->restoreState(); + + if (!oid->isInterrupted() && !oid->processTargetData()) { + LOG(ERROR) << "Problems processing target data"; + return ExitStatus::ProcessingTargetDataError; + } + } + + // Upload cache artifacts if present + if (!oid->uploadCache()) { + LOG(ERROR) << "cache upload requested and failed"; + return ExitStatus::CacheUploadError; + } + + std::cout << "SUCCESS " << fileName << std::endl; + VLOG(1) << "Entire process took " << time_ns(time_hr::now() - progStart) + << " nsecs"; + return ExitStatus::Success; +} + +int main(int argc, char *argv[]) { + int debugLevel = 1; + Oid::Config oidConfig = {}; + std::string scriptFile; + std::string scriptSource; + std::string configGenOption; + std::optional jsonPath{std::nullopt}; + + bool logAllStructs = true; + bool chaseRawPointers = false; + bool packStructs = true; + bool dumpDataSegment = false; + bool captureThriftIsset = false; + + Metrics::Tracing _("main"); +#ifndef OSS_ENABLE + folly::InitOptions init; + init.useGFlags(false); + init.removeFlags(false); + folly::init(&argc, &argv, init); +#else + google::InitGoogleLogging(argv[0]); +#endif + google::SetStderrLogging(google::WARNING); + + int c = 0; + while ((c = getopt_long(argc, argv, opts.shortOpts(), opts.longOpts(), + nullptr)) != -1) { + switch (c) { + case 'm': { + if (strcmp("prod", optarg) == 0) { + // change default settings for prod + oidConfig.hardDisableDrgn = true; + oidConfig.cacheRemoteDownload = true; + oidConfig.cacheBasePath = "/tmp/oid-cache"; + chaseRawPointers = true; + } else { + LOG(ERROR) << "Invalid mode: " << optarg << " specified!"; + usage(); + return ExitStatus::UsageError; + } + break; + } + case 'x': { + auto dataSegSizeArg = strunittol(optarg); + if (!dataSegSizeArg.has_value() || dataSegSizeArg.value() <= 0) { + LOG(ERROR) << "Invalid value specified for data buffer size"; + usage(); + return ExitStatus::UsageError; + } + oidConfig.dataSegSize = static_cast(dataSegSizeArg.value()); + break; + } + case 'p': + oidConfig.pid = atoi(optarg); + break; + case 'd': + debugLevel = atoi(optarg); + google::LogToStderr(); + google::SetStderrLogging(google::INFO); + // Enable debug logging for *only* our project, + // and not the rest of fbcode + google::SetVLOGLevel("Common", debugLevel); + google::SetVLOGLevel("Descs", debugLevel); + google::SetVLOGLevel("FuncGen", debugLevel); + google::SetVLOGLevel("GobsService", debugLevel); + google::SetVLOGLevel("ManifoldCache", debugLevel); + google::SetVLOGLevel("Metrics", debugLevel); + google::SetVLOGLevel("OICache", debugLevel); + google::SetVLOGLevel("OICodeGen", debugLevel); + google::SetVLOGLevel("OICompiler", debugLevel); + google::SetVLOGLevel("OID", debugLevel); + google::SetVLOGLevel("OIDebugger", debugLevel); + google::SetVLOGLevel("OILexer", debugLevel); + google::SetVLOGLevel("OILibrary", debugLevel); + google::SetVLOGLevel("OILibraryImpl", debugLevel); + google::SetVLOGLevel("OILogging", debugLevel); + google::SetVLOGLevel("OIOpts", debugLevel); + google::SetVLOGLevel("OIParser", debugLevel); + google::SetVLOGLevel("OIUtils", debugLevel); + google::SetVLOGLevel("PaddingHunter", debugLevel); + google::SetVLOGLevel("Serialize", debugLevel); + google::SetVLOGLevel("SymbolService", debugLevel); + google::SetVLOGLevel("TimeUtils", debugLevel); + google::SetVLOGLevel("TrapInfo", debugLevel); + google::SetVLOGLevel("TreeBuilder", debugLevel); + // Upstream glog defines `GLOG_INFO` as 0 https://fburl.com/ydjajhz0, + // but internally it's defined as 1 https://fburl.com/code/9fwams75 + gflags::SetCommandLineOption("minloglevel", "0"); + break; + case 'l': + oidConfig.enableJitLogging = true; + break; + case 'k': + oidConfig.customCodeFile = optarg; + + if (!fs::exists(oidConfig.customCodeFile)) { + LOG(ERROR) << "Non existent generated code file: " + << oidConfig.customCodeFile; + usage(); + return ExitStatus::FileNotFoundError; + } + + if (oidConfig.customCodeFile == "/tmp/tmp_oid_output_2.cpp") { + LOG(ERROR) << "Cannot use generatedCodePath:" + << oidConfig.customCodeFile; + return ExitStatus::UsageError; + } + + break; + case 'e': + oidConfig.compAndExit = true; + break; + case 'j': + oidConfig.generateJitDebug = true; + break; + case 'c': + oidConfig.configFile = std::string(optarg); + + if (!fs::exists(oidConfig.configFile)) { + LOG(ERROR) << "Non existent config file: " << oidConfig.configFile; + usage(); + return ExitStatus::FileNotFoundError; + } + + break; + case 'i': + oidConfig.debugInfoFile = std::string(optarg); + oidConfig.attachToProcess = false; + oidConfig.compAndExit = true; + + if (!fs::exists(oidConfig.debugInfoFile)) { + LOG(ERROR) << "Non existent debuginfo file: " + << oidConfig.debugInfoFile; + usage(); + return ExitStatus::FileNotFoundError; + } + + break; + case 'o': + oidConfig.cacheBasePath = optarg; + break; + case 'u': + if (strcmp(optarg, "both") == 0) { + oidConfig.cacheRemoteUpload = true; + oidConfig.cacheRemoteDownload = true; + } else if (strcmp(optarg, "upload") == 0) { + oidConfig.cacheRemoteUpload = true; + } else if (strcmp(optarg, "download") == 0) { + oidConfig.cacheRemoteDownload = true; + } else { + LOG(ERROR) << "Invalid download option: " << optarg << " specified!"; + usage(); + return ExitStatus::UsageError; + } + break; + case 'r': + oidConfig.removeMappings = true; + break; + case 'n': + chaseRawPointers = true; + break; + case 'a': + logAllStructs = true; + break; + case 'z': + packStructs = false; + break; + case 'B': + dumpDataSegment = true; + break; + case 's': + scriptFile = std::string(optarg); + break; + case 'S': + scriptSource = std::string(optarg); + break; + case 't': + oidConfig.timeout_s = atoi(optarg); + break; + case 'w': + oidConfig.genPaddingStats = false; + break; + case 'J': + jsonPath = optarg != nullptr ? optarg : "oid_out.json"; + break; + case 'T': + captureThriftIsset = true; + break; + case 'h': + default: + usage(); + return ExitStatus::Success; + } + } + + if (oidConfig.configFile.empty()) { + oidConfig.configFile = "/usr/local/share/oi/base.oid.toml"; + + if (!fs::exists(oidConfig.configFile)) { + LOG(ERROR) << "Non existent default config file: " + << oidConfig.configFile; + usage(); + return ExitStatus::FileNotFoundError; + } + + LOG(INFO) << "Using default config file " << oidConfig.configFile; + } + + if (oidConfig.pid != 0 && !oidConfig.debugInfoFile.empty()) { + LOG(INFO) << "'-p' and '-b' are mutually exclusive"; + usage(); + return ExitStatus::UsageError; + } + + if ((oidConfig.pid == 0 && oidConfig.debugInfoFile.empty()) || + oidConfig.configFile.empty()) { + usage(); + return ExitStatus::UsageError; + } + + if (!oidConfig.removeMappings && scriptFile.empty() && scriptSource.empty()) { + LOG(INFO) << "One of '-s', '-r' or '-S' must be specified"; + usage(); + return ExitStatus::UsageError; + } + + OICodeGen::Config codeGenConfig{ + .useDataSegment = true, + .chaseRawPointers = chaseRawPointers, + .packStructs = packStructs, + .genPaddingStats = oidConfig.genPaddingStats, + .captureThriftIsset = captureThriftIsset, + }; + + TreeBuilder::Config tbConfig{ + .logAllStructs = logAllStructs, + .chaseRawPointers = chaseRawPointers, + .genPaddingStats = oidConfig.genPaddingStats, + .dumpDataSegment = dumpDataSegment, + .jsonPath = jsonPath, + }; + + if (!scriptFile.empty()) { + if (!std::filesystem::exists(scriptFile)) { + LOG(ERROR) << "Non-existent script file: " << scriptFile; + return ExitStatus::FileNotFoundError; + } + std::ifstream script(scriptFile); + auto status = + runScript(scriptFile, script, oidConfig, codeGenConfig, tbConfig); + if (status != ExitStatus::Success) { + return status; + } + } else if (!scriptSource.empty()) { + std::istringstream script(scriptSource); + auto status = + runScript(scriptFile, script, oidConfig, codeGenConfig, tbConfig); + if (status != ExitStatus::Success) { + return status; + } + } + + if (Metrics::Tracing::isEnabled()) { + LOG(INFO) << "Will write metrics (" << Metrics::Tracing::isEnabled() + << ") in " << Metrics::Tracing::outputPath(); + } else { + LOG(INFO) << "Will not write any metric: " << Metrics::Tracing::isEnabled(); + } + + return ExitStatus::Success; +} diff --git a/src/OIDebugger.cpp b/src/OIDebugger.cpp new file mode 100644 index 0000000..991fd29 --- /dev/null +++ b/src/OIDebugger.cpp @@ -0,0 +1,2984 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OIDebugger.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#include + +#include "ContainerInfo.h" +#include "Metrics.h" +#include "OILexer.h" +#include "OIUtils.h" +#include "PaddingHunter.h" +#include "Syscall.h" + +#ifndef OSS_ENABLE +#include "cea/object-introspection/internal/GobsService.h" +#endif + +using namespace std; +using namespace ObjectIntrospection; + +bool OIDebugger::isGlobalDataProbeEnabled(void) const { + return std::any_of(cbegin(pdata), cend(pdata), + [](const auto &r) { return r.type == "global"; }); +} + +bool OIDebugger::parseScript(std::istream &script) { + Metrics::Tracing _("parse_script"); + + OIScanner scanner(&script); + OIParser parser(scanner, pdata); + +#if 0 + if (VLOG_IS_ON(1)) { + scanner.set_debug(1); + parser.set_debug_level(1); + } +#endif + + if (parser.parse() != 0) { + return false; + } + + if (pdata.numReqs() == 0) { + LOG(ERROR) << "No valid introspection data specified"; + return false; + } + + return true; +} + +bool OIDebugger::patchFunctions(void) { + assert(pdata.numReqs() != 0); + Metrics::Tracing _("patch_functions"); + + for (const auto &preq : pdata) { + VLOG(1) << "Type " << preq.type << " Func " << preq.func + << " Args: " << boost::join(preq.args, ","); + + if (preq.type == "global") { + /* + * processGlobal() - this function should do everything apart from + * continue the target thread. + */ + processGlobal(preq.func); + } else { + if (!functionPatch(preq)) { + LOG(ERROR) << "Failed to patch function"; + return false; + } + } + } + + return true; +} + +/* + * Single step an instruction in the target process 'pid' and leave the target + * thread stopped. Returns the current rip. + */ +uint64_t OIDebugger::singlestepInst(pid_t pid, struct user_regs_struct ®s) { + int status = 0; + + Metrics::Tracing _("single_step_inst"); + + if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0) == -1) { + LOG(ERROR) << "Error in singlestep!"; + return 0xdeadbeef; + } + + /* Error check... */ + waitpid(pid, &status, 0); + + if (!WIFSTOPPED(status)) { + LOG(ERROR) << "process not stopped!"; + } + + errno = 0; + if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) { + LOG(ERROR) << "Couldn't read registers: " << strerror(errno); + } + VLOG(1) << "rip after singlestep: " << std::hex << regs.rip; + + return regs.rip; +} + +void OIDebugger::dumpRegs(const char *text, pid_t pid, + struct user_regs_struct *regs) { + VLOG(1) << "(" << text << ")" + << " dumpRegs: pid: " << std::dec << pid << std::hex << " rip " + << regs->rip << " rbp: " << regs->rbp << " rsp " << regs->rsp + << " rdi " << regs->rdi << " rsi " << regs->rsi << " rdx " + << regs->rdx << " rbx " << regs->rbx << " rcx " << regs->rcx << " r8 " + << regs->r8 << " r9 " << regs->r9 << " r10 " << regs->r10 << " r11 " + << regs->r11 << " r12 " << regs->r12 << " r13 " << regs->r13 + << " r14 " << regs->r14 << " r15 " << regs->r15 << " rax " + << regs->rax << " orig_rax " << regs->orig_rax << " eflags " + << regs->eflags; +} + +/* + * singlestepFunc is a debugging aid for when times get desperate! It will + * single step the tgid specified in pid until it reached the address + * specified in 'real_end'. This means you'll get a register dump for + * every instruction which can be extremely useful for getting to those + * hard to reach places. + * + * Note that while this will single step a function nicely, it will also + * caue the target thread to spin forever and therefore fail the process + * because of the way we use breakpoint traps . It needs improving. + */ +bool OIDebugger::singleStepFunc(pid_t pid, uint64_t real_end) { + uint64_t addr = 0x0; + struct user_regs_struct regs {}; + uint64_t prev = 0; + + do { + prev = addr; + VLOG(1) << "singlestepFunc addr: " << std::hex << addr << " real_end " + << real_end; + addr = singlestepInst(pid, regs); + dumpRegs("singlestep", pid, ®s); + } while (addr != 0xdeadbeef && addr != real_end && prev != addr); + + return true; +} + +bool OIDebugger::setupLogFile(void) { + // 1. Copy the log file path in out text segment + // 2. Run the syscall + // 3. Store the resulting fd in the segmentConfigFile + if (!segConfig.existingConfig) { + auto logFilePath = + fs::path("/tmp") / ("oid-" + std::to_string(traceePid) + ".jit.log"); + + auto logFilePathLen = strlen(logFilePath.c_str()) + 1; + if (logFilePathLen > textSegSize) { + LOG(ERROR) << "The Log File's path " << logFilePath << " (" + << logFilePathLen << ") is too long for the text segment (" + << textSegSize << ")"; + return false; + } + + /* + * Using the text segment to store the path in the remote process' memory. + * The memory will be re-used anyway and the path will get overwritten. + */ + if (!writeTargetMemory((void *)logFilePath.c_str(), + (void *)segConfig.textSegBase, logFilePathLen)) { + LOG(ERROR) << "Failed to write Log File's path into target process"; + return false; + } + + /* + * Execute the `open(2)` syscall on the remote process. + * We use the O_SYNC flags to ensure each write we do make it on the disk. + * Another option would have been O_DSYNC, but since the logs get always + * appended to the end of the file, the file size always changes and + * we have to update the metadata everytime anyways. + * So O_SYNC won't incure a performance penalty, compared to O_DSYNC, + * and ensure we can use the metadata for future automation, etc. + */ + auto fd = remoteSyscall(segConfig.textSegBase, // path + O_CREAT | O_APPEND | O_WRONLY, // flags + S_IRUSR | S_IWUSR | S_IRGRP); // mode + if (!fd.has_value()) { + LOG(ERROR) << "Failed to open Log File " << logFilePath; + return false; + } + + segConfig.logFile = *fd; + } + + return true; +} + +bool OIDebugger::cleanupLogFile(void) { + return remoteSyscall(segConfig.logFile).has_value(); +} + +/* Set up traced process results and text segments */ +bool OIDebugger::segmentInit(void) { + /* + * TODO: change this. If setup_results_segment() fails we have to remove + * the text segment. + */ + if (!segConfig.existingConfig || segConfig.dataSegSize != dataSegSize) { + if (!segConfig.existingConfig) { + if (!setupSegment(SegType::text) || !setupSegment(SegType::data)) { + LOG(ERROR) << "setUpSegment failed!!!"; + return false; + } + + if (!setupLogFile()) { + LOG(ERROR) << "setUpLogFile failed!!!"; + return false; + } + } else { + if (!unmapSegment(SegType::data)) { + LOG(ERROR) << "Failed to unmmap data segment"; + return false; + } + if (!setupSegment(SegType::data)) { + LOG(ERROR) << "Failed to setup data segment"; + return false; + } + } + + segConfig.existingConfig = true; + segmentConfigFile.seekg(0); + segmentConfigFile.write((char *)&segConfig, sizeof(segConfig)); + + VLOG(1) << "segConfig size " << sizeof(segConfig); + + if (segmentConfigFile.fail()) { + LOG(ERROR) << "init: error in writing configFile" << configFilePath + << strerror(errno); + } + VLOG(1) << "About to flush segment config file"; + segmentConfigFile.flush(); + } + + // Using nanoseconds since epoch as the cookie value + using namespace std::chrono; + auto now = high_resolution_clock::now().time_since_epoch(); + segConfig.cookie = duration_cast>(now).count(); + + return true; +} + +/* + * Temporary config file with settings for this debugging "session". The + * notion of "session" is a bit fuzzy at the minute. + */ +void OIDebugger::createSegmentConfigFile(void) { + /* + * For now we'll just use the pid of the traced process to name the temp + * file so we can find it + */ + assert(traceePid != 0); + + segConfigFilePath = fs::temp_directory_path() / + ("oid-segconfig-" + std::to_string(traceePid)); + + if (!fs::exists(segConfigFilePath) || fs::file_size(segConfigFilePath) == 0) { + segmentConfigFile.open(segConfigFilePath, + ios::in | ios::out | ios::binary | ios::trunc); + + if (!segmentConfigFile.is_open()) { + LOG(ERROR) << "Failed to open " << segConfigFilePath << " : " + << strerror(errno); + } + + VLOG(1) << "createSegmentConfigFile: " << segConfigFilePath << " created"; + } else { + VLOG(1) << "createSegmentConfigFile: config file " << segConfigFilePath + << " already exists"; + + /* Read config */ + segmentConfigFile = + std::fstream(segConfigFilePath, ios::in | ios::out | ios::binary); + segmentConfigFile.read((char *)&segConfig, sizeof(c)); + + if (segmentConfigFile.fail()) { + LOG(ERROR) << "createSegmentConfigFile: failed to read from " + "existing config file" + << strerror(errno); + } + + VLOG(1) << "Existing Config File read: " << std::hex + << " textSegBase: " << segConfig.textSegBase + << " textSegSize: " << segConfig.textSegSize + << " jitCodeStart: " << segConfig.jitCodeStart + << " existingConfig: " << segConfig.existingConfig + << " dataSegBase: " << segConfig.dataSegBase + << " dataSegSize: " << segConfig.dataSegSize + << " replayInstBase: " << segConfig.replayInstBase + << " cookie: " << segConfig.cookie + << " logFile: " << segConfig.logFile; + + assert(segConfig.existingConfig); + } +} + +void OIDebugger::deleteSegmentConfig(bool deleteSegConfigFile) { + if (!segConfig.existingConfig) { + return; + } + + if (deleteSegConfigFile) { + std::error_code ec; + if (!fs::remove(segConfigFilePath, ec)) { + VLOG(1) << "Failed to remove segment config file " + << segConfigFilePath.c_str() << " value: " << ec.value() + << " message: " << ec.message(); + } + } + + segConfig.textSegBase = 0; + segConfig.textSegSize = 0; + segConfig.jitCodeStart = 0; + segConfig.replayInstBase = 0; + segConfig.existingConfig = false; + segConfig.dataSegBase = 0; + segConfig.dataSegSize = 0; + segConfig.cookie = 0; +} + +/* + * C++ enums are terrible, we know. There are a number of bodges that can make + * mapping enums values to strings more C++'esque * (read that as 'more + * grotesque') but this is the simple approach. + */ +std::string OIDebugger::taskStateToString(OIDebugger::StatusType status) { + /* Must reflect the order of OIDebugger::StatusType enum */ + static const std::array enumMapping{"SLEEP", "TRACED", "RUNNING", + "ZOMBIE", "DEAD", "DISK SLEEP", + "STOPPED", "OTHER", "BAD"}; + + return enumMapping[static_cast(status)]; +} + +OIDebugger::StatusType OIDebugger::getTaskState(pid_t pid) { + /* + * We often need to know (primarily for debugging) what the contents + * a tasks /proc//status "State" field is set to so we know if it + * is stopped, sleeping etc. Obviously, this is racy but is better than + * nothing. + */ + auto path = fs::path("/proc") / std::to_string(pid) / "stat"; + std::ifstream stat{path}; + if (!stat) { + return StatusType::bad; + } + + // Ignore pid and comm + stat.ignore(std::numeric_limits::max(), ' '); + stat.ignore(std::numeric_limits::max(), ' '); + + char state = '\0'; + stat >> state; + + /* XXX Need to add other states */ + OIDebugger::StatusType ret = StatusType::other; + if (state == 'S') { + ret = StatusType::sleep; + } else if (state == 't') { + ret = StatusType::traced; + } else if (state == 'R') { + ret = StatusType::running; + } else if (state == 'X') { + ret = StatusType::dead; + } else if (state == 'Z') { + ret = StatusType::zombie; + } else if (state == 'D') { + ret = StatusType::diskSleep; + } else if (state == 'T') { + ret = StatusType::stopped; + } + + return ret; +} + +/* For debug - do not remove */ +void OIDebugger::dumpAlltaskStates(void) { + VLOG(1) << "Task State Dump"; + for (auto const &p : threadList) { + auto state = getTaskState(p); + VLOG(1) << "Task " << p << " state: " << taskStateToString(state) << " (" + << static_cast(state) << ")"; + } +} + +int OIDebugger::getExtendedWaitEventType(int status) { + return (status >> 16); +} + +bool OIDebugger::isExtendedWait(int status) { + return (getExtendedWaitEventType(status) != 0); +} + +bool OIDebugger::contTargetThread(bool detach) const { + VLOG(1) << "contTargetThread: About to PTRACE_CONT pid " << std::dec + << traceePid << " detach = " << detach; + + if (detach) { + if ((ptrace(PTRACE_DETACH, traceePid, nullptr, nullptr)) < 0) { + LOG(ERROR) << "contTargetThread failed to detach pid " << traceePid << " " + << strerror(errno) << "(" << errno << ")"; + } + } else { + if ((ptrace(PTRACE_CONT, traceePid, nullptr, nullptr)) < 0) { + LOG(ERROR) << "contTargetThread failed to continue pid " << traceePid + << " " << strerror(errno) << "(" << errno << ")"; + } + } + + return true; +} + +bool OIDebugger::replayTrappedInstr(const trapInfo &t, pid_t pid, + struct user_regs_struct ®s, + struct user_fpregs_struct &fpregs) const { + /* + * Single step the original instruction which has been patched over + * with a breakpoint trap. The original instruction now resides in + * our private text mapping in the target process (see + * getReplayInstrAddr()) + */ + auto origRip = std::exchange(regs.rip, t.replayInstAddr); + + errno = 0; + if (ptrace(PTRACE_SETREGS, pid, nullptr, ®s) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " << strerror(errno); + } + + errno = 0; + if (ptrace(PTRACE_SETFPREGS, pid, nullptr, &fpregs) < 0) { + LOG(ERROR) << "Execute: Couldn't restore fp registers: " << strerror(errno); + } + + uintptr_t rip = singlestepInst(pid, regs); + + /* + * On entry to this function the current threads %rip is pointing straight + * after the int3 instruction which is a single byte opcode. The + * actual instruction that this is patched over will most likely be + * larger than that (e.g., a 'push ' is 2 bytes) and therefore + * we need to set the return %rip to point to the next instruction after + * that (we have just single stepped it). This assumes that the patched + * instruction at entry can't be a CTI which I'm not sure is valid. The + * assert() below will hopefully catch that though. + */ + + /* + * Hmm. Not sure what to do here. If the instruction is a control transfer + * instruction (CTI) (e.g., jmp, ret, call) then we need to just go with + * the rip that results from the single step. If it isn't a CTI then I + * think we should just go with the original rip. Sounds OK on the surface + * but not sure it is correct for all scenarios. + * + * The disassembler can probably tell us if this is a CTI so probably a + * cleaner solution. For now we'll just see if the current rip is + * outside of t->replayInstAddr + MAX_INST_SIZE then we assume a CTI. + */ + + if (rip > t.replayInstAddr && rip < t.replayInstAddr + 16) { + /* See comment above in processFuncEntry() for this */ + VLOG(1) << "Hit rip adjustment code in replayTrappedInstr"; + size_t origInstSize = rip - t.replayInstAddr; + /* + * x64 instructions can be 16 bytes but we only use 8 bytes of patch. I + * think things will have gone bad already but put this check here in case + * we are lucky enough to make it here with such an instruction. + */ + assert(origInstSize <= 8); + regs.rip = origRip - sizeofInt3 + origInstSize; + } + + errno = 0; + if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " << strerror(errno); + } + + return true; +} + +bool OIDebugger::locateObjectsAddresses(const trapInfo &tInfo, + struct user_regs_struct ®s) { + /* + * Write objects into prologue in target. + */ + bool ret = true; + for (const auto &arg : tInfo.args) { + auto remoteObjAddr = remoteObjAddrs.find(arg); + if (remoteObjAddr == remoteObjAddrs.end()) { + LOG(ERROR) << "Entry: failed to find remoteObjAddr! Skipping..."; + ret = false; + continue; + } + + auto addr = arg->findAddress(®s, tInfo.trapAddr); + if (!addr.has_value()) { + LOG(ERROR) << "Entry: failed to locate arg! Skipping..."; + ret = false; + continue; + } + + VLOG(1) << "Entry: arg addr: " << std::hex << *addr; + if (!writeTargetMemory((void *)(&addr.value()), + (void *)remoteObjAddr->second, sizeof(*addr))) { + LOG(ERROR) << "Entry: writeTargetMemory remoteObjAddr failed!"; + ret = false; + continue; + } + } + + return ret; +} + +OIDebugger::processTrapRet OIDebugger::processFuncTrap( + const trapInfo &tInfo, pid_t pid, struct user_regs_struct ®s, + struct user_fpregs_struct &fpregs) { + assert(tInfo.trapKind != OID_TRAP_JITCODERET); + + processTrapRet ret = OID_CONT; + + VLOG(1) << "\nProcess Function Trap for pid=" << pid << ": " << tInfo + << "\n\n"; + + auto t = std::make_shared(tInfo); + + /* Save interrupted registers into trap information */ + memcpy((void *)&t->savedRegs, (void *)®s, sizeof(t->savedRegs)); + + /* Save fpregs into trap information */ + memcpy((void *)&t->savedFPregs, (void *)&fpregs, sizeof(t->savedFPregs)); + + /* Start by locating each Target Object's address */ + if (!locateObjectsAddresses(*t, regs)) { + LOG(ERROR) << "Failed to locate all objects addresses. Aborting..."; + replayTrappedInstr(*t, pid, t->savedRegs, t->savedFPregs); + + errno = 0; + VLOG(1) << "processFuncEntry about to PTRACE_CONT pid " << std::dec << pid; + if (ptrace(PTRACE_CONT, pid, nullptr, nullptr) < 0) { + LOG(ERROR) << "processTrap: Error in PTRACE_CONT for pid " << pid << " " + << strerror(errno) << "(" << errno << ")"; + } + + return OIDebugger::OID_ERR; + } + + /* + * This is a function return site introspection trap. For now + * just grab the object address from rax but we will support + * using entry parameters as well. + * + * Return probes need handling slightly differently to entry + * probes. We may have instrumented multiple return sites and + * all of them need returning to their original instructions + * here - not just the one that caused the trap. For now just + * iterate and locate all OID_TRAP_VECT_ENTRYRET and + * OID_TRAP_VECT_RET entries and sort them out. This assumes + * that we only have single enablings which is not the long term + * strategy but OK for the short term. + */ + + /* + * If this entry trap needs a corresponding entry trap then we need it + * should be active so let's search the 'threadTrapState' map for it. + * It *must* have a corresponding active entry in 'threadTrapState' + * for this pid. + * + * XXX Think on this. We may have many threads executing in a function at + * one point in time: + * T: Thread A hits foo() entry site + * T+1: Thread B hits foo() entry site + * T+2: Thread B hits foo() return site + * T+3: Thread A hits foo() return site + * + * Currently this would be bad for several reasons but the most important + * is that we can't have one thread messing with another threads data while + * it is still manipulating it. We have to ensure we match pairs correctly + * or at least ensure we are using the correct data when we introspect + * the argument. This means that we have to get rid of that stupid + * 'segConfig.remoteObjAddr' mechanism which means the OID_TRAP_VECT_ENTRYRET + * trap needs to stash the argument address somewhere that the return + * trap can easily access it. We also need to be able to record data from + * multiple return threads executing JIT'd code for a given function. + */ + + /* Execute prologue, if the trap kind probes the target objects */ + if (t->trapKind == OID_TRAP_VECT_ENTRYRET) { + /* EntryRet traps locate Target Objects, but the capture is deferred until + * return */ + + /* + * If this is an entry point site that is a helper for a return probe + * site then all we need to do is squirel away the contents of the + * specified register and execute the patched instruction. The content + * of the register has already been saved above. + * + * First we need to ensure that there exists an associated return trap. If + * we are inconsistent for some reason then we should do nothing. Should we + * remove the trap though? If we instrument entry sites before return for + * argument processing returns then we may be in that window. We would need + * to ensure that this didn't keep happening though owing to a bug. Think + * on it. + */ + t->lifetime.rename("entry_arg"); + replayTrappedInstr(*t, pid, t->savedRegs, t->savedFPregs); + } else { + assert(t->trapKind == OID_TRAP_VECT_ENTRY || + t->trapKind == OID_TRAP_VECT_RET); + + if (t->trapKind == OID_TRAP_VECT_ENTRY) { + t->lifetime.rename("entry_jit"); + } else { + t->lifetime.rename("return_jit"); + } + + /* Execute from the start of the prologue */ + regs.rip = t->prologueObjAddr; + t->fromVect = true; + + VLOG(1) << "processTrap: redirect pid " << std::dec << pid << " to address " + << std::hex << (void *)tInfo.prologueObjAddr << " tInfo: " << tInfo + << " " << tInfo.prologueObjAddr << " " << tInfo.fromVect; + + /* + * A NOTE ON SINGLE DATA OBJECT USAGE: + * + * This is so busted. Every thread that piles through this entry + * point gets its register contents written to the single place + * as we only look at a single object - we then continue the thread + * with its %rip set to the start of the JIT code. So it's possible + * (and likely in busy code) that mutiple threads could have come through + * here and rewritten the single 'remoteObjAddr' location. They may + * well end up not measuring their own object. That is really bad and + * we need to go to a per-thread object addresss store in the remote target + * process before this is let loose on the wider worl outside of FB. + */ + errno = 0; + if (ptrace(PTRACE_SETREGS, pid, nullptr, ®s) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " << strerror(errno); + } + } + + VLOG(1) << "Inserting Trapinfo for pid " << std::dec << pid; + threadTrapState.insert_or_assign(pid, std::move(t)); + + errno = 0; + VLOG(1) << "processFuncTrap about to PTRACE_CONT pid " << std::dec << pid; + if (ptrace(PTRACE_CONT, pid, nullptr, nullptr) < 0) { + LOG(ERROR) << "processTrap: Error in PTRACE_CONT for pid " << pid << " " + << strerror(errno) << "(" << errno << ")"; + } + + VLOG(1) << "Finished Function Trap processing\n"; + + return ret; +} + +OIDebugger::processTrapRet OIDebugger::processJitCodeRet( + const trapInfo &tInfo __attribute__((unused)), pid_t pid) { + OIDebugger::processTrapRet ret = OIDebugger::OID_CONT; + + assert(tInfo.trapKind == OID_TRAP_JITCODERET); + + VLOG(1) << "Process JIT Code Return Trap"; + + /* + * This is a trap from a non-vectored route. If an entry for this thread + * exists in the threadTrapState map then this trap is the servicing of a + * previously handled vector OID_TRAP_VECT_ENTRY. If so, redirect the thread + * back to the original call site. If not, just let into continue. + */ + if (auto iter = threadTrapState.find(pid); + iter != std::end(threadTrapState)) { + /* + * This is the return path from JIT code. Before we re-vector the target + * thread back to its original code path we must replay the instruction + * that has been patched over by the breakpoint trap so that its effects + * are visible to the target thread. + */ + auto t{iter->second}; + t->lifetime.stop(); + + auto jitTrapProcessTime = Metrics::Tracing("jit_ret"); + + VLOG(1) << "Hit the return path from vector. Redirect to " << std::hex + << t->trapAddr; + + /* + * Global variable handling sits outside the regular scheme and requires + * a lot less work than other traps. + */ + if (t->trapAddr != GLOBAL_VARIABLE_TRAP_ADDR) { + replayTrappedInstr(*t, pid, t->savedRegs, t->savedFPregs); + } else { + VLOG(1) << "processJitCodeRet processing global variable return"; + + errno = 0; + if (ptrace(PTRACE_SETREGS, pid, nullptr, &t->savedRegs) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " + << strerror(errno); + } + + errno = 0; + if (ptrace(PTRACE_SETFPREGS, pid, nullptr, &t->savedFPregs) < 0) { + LOG(ERROR) << "Execute: Couldn't restore fp registers: " + << strerror(errno); + } + + if (VLOG_IS_ON(1)) { + errno = 0; + struct user_regs_struct newregs {}; + if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " + << strerror(errno); + } + + dumpRegs("processJitCodeRet global: ", pid, &newregs); + } + } + + VLOG(1) << "Erasing Trapinfo for pid " << std::dec << iter->first; + threadTrapState.erase(iter); + + VLOG(1) << "processTrap2 processing PTRACE_CONT pid " << pid; + + jitTrapProcessTime.stop(); + + if (ptrace(PTRACE_CONT, pid, nullptr, nullptr) < 0) { + LOG(ERROR) << "processTrap2: Error in PTRACE_CONT for pid " << pid << " " + << strerror(errno) << "(" << errno << ")"; + } + + if (++count == 1 || isInterrupted()) { + VLOG(1) << "count: " << count << " oid done"; + ret = OIDebugger::OID_DONE; + } else { + ret = OIDebugger::OID_CONT; + } + } + + VLOG(1) << "Finished JIT Code Return trap processing\n"; + + return ret; +} + +/* + * Although we follow the same naming scheme as for other probe types + * (e.g., entry/return), this function is never called from trap handling + * code. Global variables do not rely on a specific entry point to be + * instrumented but instead we can simply hijack a thread (the main thread + * in this case) and introspect the global data. It would be good if we had + * a cheap way of asserting that the global thread is stopped. + */ +bool OIDebugger::processGlobal(const std::string &varName) { + assert(mode == OID_MODE_THREAD); + + VLOG(1) << "Introspecting global variable: " << varName; + + errno = 0; + if (ptrace(PTRACE_SYSCALL, traceePid, nullptr, nullptr) < 0) { + LOG(ERROR) << "Couldn't attach to target pid " << traceePid + << " (Reason: " << strerror(errno) << ")"; + return false; + } + + VLOG(1) << "About to wait for process on syscall entry/exit"; + int status = 0; + waitpid(traceePid, &status, 0); + + if (!WIFSTOPPED(status)) { + LOG(ERROR) << "process not stopped!"; + } + + errno = 0; + struct user_regs_struct regs {}; + if (ptrace(PTRACE_GETREGS, traceePid, nullptr, ®s) < 0) { + LOG(ERROR) << "processGlobal: failed to read registers" << strerror(errno); + return false; + } + + errno = 0; + struct user_fpregs_struct fpregs {}; + if (ptrace(PTRACE_GETFPREGS, traceePid, nullptr, &fpregs) < 0) { + LOG(ERROR) << "processGlobal: Couldn't get fp registers: " + << strerror(errno); + } + + dumpRegs("After syscall stop", traceePid, ®s); + + auto t = std::make_shared(OID_TRAP_JITCODERET, + GLOBAL_VARIABLE_TRAP_ADDR); + t->lifetime.rename("global_jit"); + threadTrapState.emplace(traceePid, t); + + regs.rip -= 2; + /* Save interrupted registers into trap information */ + memcpy((void *)&t->savedRegs, (void *)®s, sizeof(t->savedRegs)); + + /* Save fpregs into trap information */ + memcpy((void *)&t->savedFPregs, (void *)&fpregs, sizeof(t->savedFPregs)); + regs.rip = segConfig.textSegBase; + + dumpRegs("processGlobal2", traceePid, ®s); + + /* + * Get the variable address and push it into the target process patch area. + */ + auto sym = symbols->locateSymbol(varName); + if (!sym.has_value()) { + LOG(ERROR) << "processGlobal: failed to get global's address!"; + return false; + } + + uint64_t addr = sym->addr; + + auto gd = symbols->findGlobalDesc(varName); + if (!gd) { + LOG(ERROR) << "processGlobal: failed to find GlobalDesc!"; + return false; + } + + auto remoteObjAddr = remoteObjAddrs.find(gd); + if (remoteObjAddr == remoteObjAddrs.end()) { + LOG(ERROR) << "processGlobal: no remote object addr for " << varName; + return false; + } + + if (!writeTargetMemory((void *)&addr, (void *)remoteObjAddr->second, + sizeof(addr))) { + LOG(ERROR) << "processGlobal: writeTargetMemory remoteObjAddr failed!"; + } + + VLOG(1) << varName << " addr: " << std::hex << addr; + + /* Main target thread should already be stopped */ + + errno = 0; + if (ptrace(PTRACE_SETREGS, traceePid, nullptr, ®s) < 0) { + LOG(ERROR) << "Execute: Couldn't restore registers: " << strerror(errno); + } + + errno = 0; + if (ptrace(PTRACE_CONT, traceePid, nullptr, nullptr) < 0) { + VLOG(1) << "processTrap: Error in PTRACE_CONT for pid " << traceePid << " " + << strerror(errno) << "(" << errno << ")"; + } + + return true; +} + +bool OIDebugger::canProcessTrapForThread(pid_t thread_pid) const { + /* + * We want to prevent multiple threads from running the JIT code at the same + * time. We have only one data segment, so we don't support concurrent + * probes, for the moment. This method checks if we have a probe already + * running. It uses the threadTrapState map to do so. If the map is empty, + * no probe is running, so we return true. Otherwise, we must check if the + * the thread which encountered the trap is part of the map. If the new trap + * is from the same thread, we also return true. This is to handle entry-ret + * probes and the JIT code ret trap. + */ + if (threadTrapState.empty()) { + return true; + } + return threadTrapState.find(thread_pid) != end(threadTrapState); +} + +/* + * Continue the specified process and wait for a thread to stop and process + * whatever caused it to stop. Which thread is waited for is controlled by + * the 'anyPid' parameter: if true (default) we wait for any thread to stop, + * if false we wait for the thread specified by the first parameter 'pid' to + * stop (yes, I know that's some really bad naming). + */ +OIDebugger::processTrapRet OIDebugger::processTrap(pid_t pid, bool blocking, + bool anyPid) { + int status = 0; + pid_t newpid = 0; + processTrapRet ret = OIDebugger::OID_CONT; + pid_t pidToWaitFor = -1; + + if (threadList.empty()) { + LOG(ERROR) + << "Thread list is empty: the target process must have died. Abort..."; + return OID_DONE; + } + + VLOG(1) << "processTrap: About to PTRACE_CONT pid " << std::dec << pid; + + if (ptrace(PTRACE_CONT, pid, nullptr, nullptr) < 0) { + OIDebugger::StatusType s = getTaskState(pid); + VLOG(1) << "processTrap1: Error in PTRACE_CONT for pid " << pid << " " + << strerror(errno) << "(" << errno << ") status " + << taskStateToString(s) << " (" << static_cast(s) << ")"; + + if (errno != ESRCH) { + return OIDebugger::OID_ERR; + } + } + + if (!anyPid) { + pidToWaitFor = pid; + VLOG(1) << "About to wait for pid " << std::dec << pidToWaitFor; + } else { + VLOG(1) << "About to wait for any child process "; + } + + errno = 0; + int options = blocking ? 0 : WNOHANG; + if ((newpid = waitpid(pidToWaitFor, &status, options)) < 0) { + LOG(ERROR) << "processTrap: Error in waitpid (pid " << pidToWaitFor << ")" + << " " << strerror(errno); + + /* SIGINT handling */ + if (errno == EINTR && isInterrupted()) { + return OIDebugger::OID_DONE; + } + } + + if (newpid == 0) { + /* WNOHANG handling */ + VLOG(1) << "No child waiting for processTrap"; + return OIDebugger::OID_DONE; + } + + if (!WIFSTOPPED(status)) { + LOG(ERROR) << "contAndExecute: process not stopped!"; + return blocking ? OID_ERR : OID_CONT; + } + siginfo_t info; + + auto stopsig = WSTOPSIG(status); + VLOG(1) << "Stop sig: " << std::dec << stopsig; + if (ptrace(PTRACE_GETSIGINFO, newpid, nullptr, &info) < 0) { + LOG(ERROR) << "PTRACE_GETSIGINFO failed with " << strerror(errno); + } else { + bool stopped = !!info.si_status; + VLOG(1) << "PTRACE_GETSIGINFO pid " << newpid << " signo " << info.si_signo + << " code " << info.si_code << " status " << info.si_status + << " stopped: " << stopped; + } + + /* + * Process PTRACE_EVENT stops (extended waits). + */ + if (isExtendedWait(status)) { + int type = getExtendedWaitEventType(status); + + if (type == PTRACE_EVENT_CLONE || type == PTRACE_EVENT_FORK || + type == PTRACE_EVENT_VFORK) { + unsigned long ptraceMsg = 0; + if (ptrace(PTRACE_GETEVENTMSG, newpid, NULL, &ptraceMsg) < 0) { + LOG(ERROR) << "processTrap: PTRACE_GETEVENTMSG failed: " + << strerror(errno); + } + + pid_t childPid = static_cast(ptraceMsg); + VLOG(1) << "New child created. pid " << std::dec << childPid + << " (type: " << type << ")"; + threadList.push_back(childPid); + + /* + * ptrace() semantics are opaque to say the least. The new child + * has a pending SIGSTOP so we need to relieve it of that burden. + */ + int tstatus = 0; + int tret = 0; + tret = waitpid(childPid, &tstatus, __WALL | WSTOPPED | WEXITED); + + if (tret == -1) { + VLOG(1) << "processTrap: Error waiting for new child " << std::dec + << childPid << " (Reason: " << strerror(errno) << ")"; + } else if (tret != childPid) { + VLOG(1) << "processTrap: Unexpected pid waiting for child: " << std::dec + << tret; + } else if (!WIFSTOPPED(tstatus)) { + VLOG(1) << "processTrap: Unexpected status returned when " + "waiting for child: " + << std::hex << tstatus; + } + + if (WIFSTOPPED(tstatus)) { + VLOG(1) << "child was stopped with: " << WSTOPSIG(tstatus); + } + + VLOG(1) << "About to PTRACE_CONT pid " << std::dec << childPid; + + ptrace(PTRACE_SETOPTIONS, childPid, NULL, + PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE | + PTRACE_O_TRACEVFORK); + + if (ptrace(PTRACE_CONT, childPid, nullptr, nullptr) < 0) { + VLOG(1) << "processTrap2: Error in PTRACE_CONT for pid " << childPid + << " " << strerror(errno) << "(" << errno << ")"; + } + } else if (type == PTRACE_EVENT_EXIT) { + VLOG(1) << "Thread exiting!! pid " << std::dec << newpid; + threadList.erase( + std::remove(threadList.begin(), threadList.end(), newpid), + threadList.end()); + } else if (type == PTRACE_EVENT_STOP) { + VLOG(1) << "PTRACE_EVENT_STOP!"; + } else { + VLOG(1) << "unhandled extended wait event type: " << type; + } + + VLOG(1) << "extended wait About to cont pid " << newpid; + if (ptrace(PTRACE_CONT, newpid, nullptr, nullptr) < 0) { + VLOG(1) << "processTrap2: Error in PTRACE_CONT for pid " << newpid << " " + << strerror(errno) << "(" << errno << ")"; + } + + return ret; + } + + int sig = WSTOPSIG(status); + switch (sig) { + /* + * The handling of fatal signals for the child needs to be implemented + * in a more complete manner: be exhaustive in types of signals handled + * and also display diagnostics. However, for now this will do. One of + * the massive advantages of controlling a target via the ptrace + * interface is that fatal signals are not delivered to the target but + * come to us first. This means that we can pretty much do whatever we + * want in our jitted code and get away with it... + * + * TODO: Ensure signal coverage is complete + */ + case SIGALRM: { + VLOG(1) << "SIGALRM received!!"; + if (ptrace(PTRACE_CONT, newpid, nullptr, SIGALRM) < 0) { + VLOG(1) << "processTrap2: Error in PTRACE_CONT SIGALRM for pid " + << newpid << " " << strerror(errno) << "(" << errno << ")"; + } + break; + } + + case SIGSEGV: { + { + errno = 0; + struct user_regs_struct regs {}; + if (ptrace(PTRACE_GETREGS, newpid, nullptr, ®s) < 0) { + LOG(ERROR) << "SIGSEGV handling: failed to read registers" + << strerror(errno); + } + + dumpRegs("SIGSEGV", newpid, ®s); + LOG(ERROR) << "SIGSEGV: faulting addr " << std::hex << info.si_addr + << " rip " << regs.rip << " (pid " << std::dec << newpid + << ")"; + } + + if (auto iter{threadTrapState.find(newpid)}; + iter != std::end(threadTrapState)) { + auto t{iter->second}; + + LOG(ERROR) << "SEGV handling trap state found for " << std::dec + << newpid; + + errno = 0; + if (ptrace(PTRACE_SETREGS, newpid, NULL, &t->savedRegs) < 0) { + LOG(ERROR) << "processTrap: Couldn't restore registers: " + << strerror(errno); + } + dumpRegs("After1", newpid, &t->savedRegs); + + errno = 0; + if (ptrace(PTRACE_SETFPREGS, newpid, NULL, &t->savedFPregs) < 0) { + LOG(ERROR) << "processTrap: Couldn't restore fp registers: " + << strerror(errno); + } + + replayTrappedInstr(*t, newpid, t->savedRegs, t->savedFPregs); + threadTrapState.erase(newpid); + + errno = 0; + if (ptrace(PTRACE_CONT, newpid, nullptr, nullptr) < 0) { + VLOG(1) << "processTrap: Error in PTRACE_CONT for pid " << newpid + << " " << strerror(errno) << "(" << errno << ")"; + } + } else { + // We are not the source of the SIGSEGV so forward it to the target. + errno = 0; + if (ptrace(PTRACE_CONT, newpid, nullptr, sig) < 0) { + VLOG(1) << "processTrap: Error in PTRACE_CONT for pid " << newpid + << " " << strerror(errno) << "(" << errno << ")"; + } + } + return OIDebugger::OID_DONE; + } + + case SIGBUS: { + LOG(ERROR) << "SIGBUS: " << std::hex << info.si_addr << "(pid " << newpid + << ")"; + return OIDebugger::OID_DONE; + } + + case SIGCHLD: { + VLOG(1) << "SIGCHLD processing for pid " << newpid; + if (ptrace(PTRACE_CONT, newpid, nullptr, SIGCHLD) < 0) { + VLOG(1) << "processTrap2: Error in PTRACE_CONT SIGCHLD for pid " << pid + << " " << strerror(errno) << "(" << errno << ")"; + } + + break; + } + + case SIGTRAP: { + /* + * Posix dictates that siginfo.si_addr is only used for SIGILL, + * SIGFPE, SIGSEGV and SIGBUS. Use PTRACE_GETREGS for SIGTRAP. + * + * We use INT3 for several purposes: in segment setup, + * instrumentation code execution and in pid provider style + * entry/return point vectoring. We therefore must be able to match + * which interrupt this is for and act accordingly. + */ + struct user_regs_struct regs {}; + struct user_fpregs_struct fpregs {}; + + errno = 0; + if (ptrace(PTRACE_GETREGS, newpid, nullptr, ®s) < 0) { + LOG(ERROR) << "processTrap failed to read registers for process " + << traceePid << " " << strerror(errno); + + return OIDebugger::OID_ERR; + } + + errno = 0; + if (ptrace(PTRACE_GETFPREGS, newpid, nullptr, &fpregs) < 0) { + LOG(ERROR) << "processTrap failed to read fp regs for process " + << traceePid << " " << strerror(errno); + + return OIDebugger::OID_ERR; + } + + /* + * If we do not remove the breakpoint trap instructions then we + * need to not adjust the %rip and ensure we replay the original + * instruction that has been patched before we return the thread + * to it's next instruction. + */ + uint64_t bpaddr = regs.rip - sizeofInt3; + + if (auto it{activeTraps.find(bpaddr)}; it != std::end(activeTraps)) { + /* + * This HAS TO BE a COPY and not a reference! removeTrap() would + * otherwise delete the only instance of the std::shared_ptr, leading + * to the destruction of the tInfo and many use-after-free! + */ + auto tInfo = it->second; + assert(bpaddr == tInfo->trapAddr); + + if (!blocking || !canProcessTrapForThread(newpid)) { + /* + * Only one probe is allowed to run at a time. We skip the other + * traps by replaying their instruction and continuing their thread. + */ + if (blocking) { + VLOG(1) + << "Another probe already running, skipping trap for thread " + << newpid; + } else { + VLOG(1) << "Resuming thread " << newpid; + } + + replayTrappedInstr(*tInfo, newpid, regs, fpregs); + + if (ptrace(PTRACE_CONT, newpid, nullptr, nullptr) < 0) { + LOG(ERROR) << "processTrap: Error in PTRACE_CONT for pid " << newpid + << " " << strerror(errno) << "(" << errno << ")"; + } + + break; + } + + /* Remove the trap right before we process it */ + removeTrap(newpid, *tInfo); + + if (tInfo->trapKind == OID_TRAP_JITCODERET) { + ret = processJitCodeRet(*tInfo, newpid); + } else { + ret = processFuncTrap(*tInfo, newpid, regs, fpregs); + } + } else { + LOG(ERROR) << "Error! SIGTRAP: 0x" << std::hex << bpaddr + << " No activeTraps entry found, resuming thread " + << std::dec << newpid; + + // Resuming at the breakpoint + regs.rip = bpaddr; + + errno = 0; + if (ptrace(PTRACE_SETREGS, newpid, NULL, ®s) < 0) { + LOG(ERROR) << "processTrap failed to set registers for process " + << newpid << " " << strerror(errno); + } + + errno = 0; + if (ptrace(PTRACE_CONT, newpid, nullptr, nullptr) < 0) { + LOG(ERROR) << "processTrap: Error in PTRACE_CONT for pid " << newpid + << " " << strerror(errno) << "(" << errno << ")"; + } + } + + break; + } + + case SIGILL: { + VLOG(1) << "OOPS! SIGILL received for pid " << newpid << " addr: 0x" + << std::hex << info.si_addr; + + break; + } + + default: + LOG(ERROR) << "contAndExecute: Explictly unhandled signal. Forwarding " + << strsignal(WSTOPSIG(status)); + + if (ptrace(PTRACE_CONT, newpid, nullptr, sig) < 0) { + VLOG(1) << "processTrap2: Error in PTRACE_CONT SIGALRM for pid " << pid + << " " << strerror(errno) << "(" << errno << ")"; + } + break; + } + + return ret; +} + +std::optional> OIDebugger::findRetLocs(FuncDesc &fd) { + size_t maxSize = std::accumulate( + fd.ranges.begin(), fd.ranges.end(), size_t(0), + [](auto currMax, auto &r) { return std::max(currMax, r.size()); }); + + std::vector retLocs; + std::vector text(maxSize); + for (const auto &range : fd.ranges) { + /* + * We already have enough capacity to accomodate any range from the function + * But we must ensure the actual `size` of the vector matches what is being + * put into it. `locateOpcode` uses the vector's size to know for how long + * it must decode instructions. As resize doesn't never reduces the capacity + * of the vector, there is no risk of multiple memory allocations impacting + * the performances. + */ + text.resize(range.size()); + + /* Copy the range of instruction into the text vector to be disassembled */ + if (!readTargetMemory((void *)range.start, text.data(), text.size())) { + LOG(ERROR) << "Could not read function range " << fd.symName << "@" + << range; + return std::nullopt; + } + + /* + * Locate the returns within the function instructions. + * Immediate values have a size of 16-bits/2-bytes. Both 0xC2 and 0xCA have + * a total size of 3 bytes, which fit in the 8 bytes window we capture with + * PTRACE_PEEKTEXT. So we'll be able to capture and replay the instraction + * without problem. We assume a flat memory model, meaning we only have 1 + * segment and we don't have returns across segments. So we don't have to + * convert RETN into RETF during replay. We can't probe the return fo + * tail-recursive functions as they typically ends with a JMP. + */ + // clang-format off + const std::array retInsts = { + std::array{uint8_t(0xC2)}, /* Return from near procedure with immediate value */ + std::array{uint8_t(0xC3)}, /* Return from near procedure */ + std::array{uint8_t(0xCA)}, /* Return from far procedure with immediate value */ + std::array{uint8_t(0xCB)}, /* Return from far procedure */ + }; + // clang-format on + auto locs = OICompiler::locateOpcodes(text, retInsts); + if (!locs.has_value()) { + LOG(ERROR) + << "Failed to locate all Return instructions in function range " + << fd.symName << "@" << range; + return std::nullopt; + } + + /* Add the range.start to the offsets returned above to get the absolute + * address of the return instructions */ + for (auto loc : *locs) { + retLocs.push_back(range.start + loc); + } + } + + return retLocs; +} + +/* + * Locate the address in the target address space where the patched + * instruction that was at address 'addr' is found. We currently don't handle + * instructions larger than 8 bytes. + * + * If it's not in the replayInstMap, return the address to the next free entry + * in the cache and put the entry in the map. + */ +std::optional OIDebugger::nextReplayInstrAddr(const trapInfo &t) { + if (auto it = replayInstMap.find(t.trapAddr); it != end(replayInstMap)) { + VLOG(1) << "Found instruction for trap " << (void *)t.trapAddr + << " at address " << (void *)it->second; + return it->second; + } + + /* + * Not found so allocate the instruction into the replay instruction area and + * create a map entry. + */ + + VLOG(1) << "Replay Instruction Base " << std::hex << segConfig.replayInstBase; + auto newInstrAddr = segConfig.replayInstBase + replayInstsCurIdx++ * 8; + if (newInstrAddr >= segConfig.textSegBase + segConfig.textSegSize) { + LOG(ERROR) << "Text Segment's Replay Instruction buffer is full. Increase " + "replayInstSize in OIDebugger.h"; + return std::nullopt; + } + + VLOG(1) << "Orig instructions for trap " << (void *)t.trapAddr + << " will get saved at " << (void *)newInstrAddr; + replayInstMap.emplace(t.trapAddr, newInstrAddr); + + return newInstrAddr; +} + +/* + * Insert a breakpoint trap at the first instruction of the function + * supplied as the argument. + * + * This is a hybrid instrumentation function for entry-return patching. Pulling + * the entry and return patching together for this case does create a + * maintenance burden as we now have code doing exactly the same thing in + * two different places However, it allows us to reduce the time window + * between enabling entry and return sites (i.e., when we actually install + * breakpoint traps). + */ + +/* + * This is a first pass at function return tracing. What does that mean + * exactly. Well, we allow introspection of the actual return value or we + * allow introspection of one of the input parameters *at return*. This + * causes significant complications to what is already looking like a very + * problematic problem (at least from a concurrency perspective). + * + * Currently I can't see a way to obtain function parameters without using + * breakpoints at the function entry and of course this is a real pain. + * Another problem is that we may well have multiple return locations + * within a function so we need multiple points of instrumentation. The + * main issue I see with this is how to do "atomically" and this has multiple + * meanings according to context: + * - a thread executing instructions. Currently the only option we have + * for modifying read/execute text is via ptrace and this does 8 bytes + * at a time. How atomic is that 8 byte write w.r.t threads that are + * executing code within that region? Needs investigation but I'd be very + * surprised if it was truly atomic. + * - w.r.t Object Introspection. This is easier to deal with than the above + * problem but we have to have a consistent view within OI. This means that + * until all our ret's are instrumented (and possibly our entry point also) + * then we can't allow a thread to go down the introspection path. All + * instrumentation much be done as a single unit. + */ + +bool OIDebugger::functionPatch(const prequest &req) { + assert(req.type != "global"); + + auto fd = symbols->findFuncDesc(req.getReqForArg(0)); + if (!fd) { + LOG(ERROR) << "Could not find FuncDesc for " << req.func; + return false; + } + + /* + * Patch the function according to the given request: + * 1. Locate all TRAP points and create a corresponding empty trapInfo in + * tiVec. + * 2. Read the original instructions in their corresponding trapInfo. + * 3. Finish building the trapInfo with the info collected above. + * 4. Save the original instructions in our Replay Instruction buffer. + * 5. Insert the traps in the target process. + */ + + std::vector> tiVec; + + /* 1. Locate all TRAP points and create a corresponding empty trapInfo in + * tiVec */ + bool hasArg = std::any_of(begin(req.args), end(req.args), + [](auto &arg) { return arg != "retval"; }); + + if (req.type == "entry" || hasArg) { + trapType tType = + req.type == "return" ? OID_TRAP_VECT_ENTRYRET : OID_TRAP_VECT_ENTRY; + uintptr_t trapAddr = fd->ranges[0].start; + if (req.args[0].starts_with("arg") || req.args[0] == "this") { + auto *argument = + dynamic_cast(fd->getArgument(req.args[0]).get()); + if (argument->locator.locations_size > 0) { + /* + * The `std::max` is necessary because sometimes when a binary is + * compiled in debug-mode, the compiler will state that the variable + * becomes live at address 0, which is clearly not what we want. + */ + trapAddr = std::max(trapAddr, argument->locator.locations[0].start); + } + } + tiVec.push_back( + std::make_shared(tType, trapAddr, segConfig.textSegBase)); + } + + if (req.type == "return") { + auto retLocs = findRetLocs(*fd); + if (!retLocs.has_value() || retLocs.value().empty()) { + return false; + } + + for (auto addr : *retLocs) { + tiVec.push_back(std::make_shared(OID_TRAP_VECT_RET, addr, + segConfig.textSegBase)); + } + } + + assert(!tiVec.empty()); + + /* 2. Read the original instructions in their corresponding trapInfo */ + std::vector localIov; + std::vector remoteIov; + localIov.reserve(tiVec.size()); + remoteIov.reserve(tiVec.size()); + + for (auto &ti : tiVec) { + localIov.push_back({(void *)ti->origTextBytes, sizeof(ti->origTextBytes)}); + remoteIov.push_back({(void *)ti->trapAddr, sizeof(ti->origTextBytes)}); + } + + errno = 0; + auto readBytes = process_vm_readv(traceePid, localIov.data(), localIov.size(), + remoteIov.data(), remoteIov.size(), 0); + if (readBytes < 0) { + LOG(ERROR) << "Failed to get original instructions: " << strerror(errno); + return false; + } + + if ((size_t)readBytes != (tiVec.size() * sizeof(trapInfo::origTextBytes))) { + LOG(ERROR) << "Failed to get all original instructions!"; + return false; + } + + /* 3. Finish building the trapInfo with the info collected above */ + + /* Re-use remoteIov to write the original instructions in our textSegment */ + remoteIov.clear(); + + for (auto &trap : tiVec) { + trap->patchedText = trap->origText; + trap->patchedTextBytes[0] = int3Inst; + + auto replayInstrAddr = nextReplayInstrAddr(*trap); + if (!replayInstrAddr.has_value()) { + LOG(ERROR) + << "Failed to allocate room for replay instructions in text segment"; + return false; + } + + trap->replayInstAddr = *replayInstrAddr; + remoteIov.push_back( + {(void *)trap->replayInstAddr, sizeof(trap->origTextBytes)}); + + if (trap->trapKind == OID_TRAP_VECT_ENTRY || + trap->trapKind == OID_TRAP_VECT_ENTRYRET) { + /* Capture the arguments to probe */ + trap->args.reserve(req.args.size()); + for (const auto &arg : req.args) { + if (auto targetObj = fd->getArgument(arg)) { + trap->args.push_back(std::move(targetObj)); + } else { + LOG(ERROR) << "Failed to get argument " << arg << " for function " + << req.func; + return false; + } + } + } else if (trap->trapKind == OID_TRAP_VECT_RET) { + /* Capture retval, if this is a return-retval req */ + if (std::find(begin(req.args), end(req.args), "retval") != + end(req.args)) { + trap->args.push_back(fd->retval); + } + } + } + + /* 4. Save the original instructions in our Replay Instruction buffer */ + errno = 0; + auto writtenBytes = + process_vm_writev(traceePid, localIov.data(), localIov.size(), + remoteIov.data(), remoteIov.size(), 0); + if (writtenBytes < 0) { + LOG(ERROR) << "Failed to save original instructions: " << strerror(errno); + return false; + } + + if ((size_t)writtenBytes != + (tiVec.size() * sizeof(trapInfo::origTextBytes))) { + LOG(ERROR) << "Failed to save all original instructions!"; + return false; + } + + /* 5. Insert the traps in the target process */ + for (const auto &trap : tiVec) { + VLOG(1) << "Patching function " << req.func << " @" + << (void *)trap->trapAddr; + activeTraps.emplace(trap->trapAddr, trap); + + errno = 0; + if (ptrace(PTRACE_POKETEXT, traceePid, trap->trapAddr, trap->patchedText) < + 0) { + /* We'll let our cleanup handling restore the original instructions */ + LOG(ERROR) << "functionPatch POKETEXT failed: " << strerror(errno); + return false; + } + } + + return true; +} + +/* See "syscall.h" for an explanation on the `Sys` template argument */ +template +std::optional OIDebugger::remoteSyscall(Args... _args) { + /* Check the number of arguments received match the syscall's requirement */ + static_assert(sizeof...(_args) == Sys::ArgsCount, + "Wrong number of arguments"); + + Metrics::Tracing _("syscall"); + VLOG(1) << "syscall enter " << Sys::Name; + + auto sym = symbols->locateSymbol("main"); + if (!sym.has_value()) { + LOG(ERROR) << "Failed to get address for 'main'"; + return std::nullopt; + } + + uint64_t patchAddr = sym->addr; + VLOG(1) << "Address of main: " << (void *)patchAddr; + + /* Saving current registers states */ + errno = 0; + struct user_regs_struct oldregs {}; + if (ptrace(PTRACE_GETREGS, traceePid, nullptr, &oldregs) < 0) { + LOG(ERROR) << "syscall: GETREGS failed for process " << traceePid << ": " + << strerror(errno); + return std::nullopt; + } + + errno = 0; + struct user_fpregs_struct oldfpregs {}; + if (ptrace(PTRACE_GETFPREGS, traceePid, nullptr, &oldfpregs) < 0) { + LOG(ERROR) << "syscall: GETFPREGS failed for process " << traceePid << ": " + << strerror(errno); + return std::nullopt; + } + + /* + * Ensure we restore the registers before returning! BOOST_SCOPE_EXIT_ALL + * sets up an object whose destructor executes the block below. Thanks to + * RAII, it ensures that the block always gets executed, independently of + * early returns or exception being thrown! This allow us to keep writing + * code without having to keep "restoring the registers" in mind. + */ + BOOST_SCOPE_EXIT_ALL(&) { + errno = 0; + if (ptrace(PTRACE_SETREGS, traceePid, nullptr, &oldregs) < 0) { + VLOG(1) << "syscall: restore SETREGS failed: " << strerror(errno); + } + + errno = 0; + if (ptrace(PTRACE_SETFPREGS, traceePid, nullptr, &oldfpregs) < 0) { + LOG(ERROR) << "syscall: restore SETFPREGS failed: " << strerror(errno); + } + }; + + /* Prepare new registers to hijack the thread and execute the syscall */ + struct user_regs_struct newregs = oldregs; + newregs.rip = patchAddr + 1; // +1 to skip the INT3 safeguard + newregs.rax = Sys::SysNum; + + { + /* + * See syscall(2) for the list of registers used for syscalls + * arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 + * x86-64 rdi rsi rdx r10 r8 r9 - + */ + const std::array argToReg = { + &newregs.rdi, &newregs.rsi, &newregs.rdx, + &newregs.r10, &newregs.r8, &newregs.r9, + }; + + unsigned long long args[] = {(unsigned long long)_args...}; + for (size_t argIdx = 0; argIdx < Sys::ArgsCount; ++argIdx) { + *argToReg[argIdx] = args[argIdx]; + } + } + + /* Set the new registers with our syscall arguments */ + errno = 0; + if (ptrace(PTRACE_SETREGS, traceePid, nullptr, &newregs)) { + LOG(ERROR) << "syscall: SETREGS failed: " << strerror(errno); + return std::nullopt; + } + + /* Save the instructions so they can be restored */ + errno = 0; + long oldinsts = ptrace(PTRACE_PEEKTEXT, traceePid, patchAddr, nullptr); + if (errno != 0) { + LOG(ERROR) << "syscall: PEEKTEXT failed: " << strerror(errno); + return std::nullopt; + } + + /* + * Ensure we restore the instructions before returning! + * See the BOOST_SCOPE_EXIT_ALL above for more explanations... + */ + BOOST_SCOPE_EXIT_ALL(&) { + errno = 0; + if (ptrace(PTRACE_POKETEXT, traceePid, patchAddr, oldinsts) < 0) { + LOG(ERROR) << "syscall: restore POKETEXT failed: " << strerror(errno); + } + }; + + /* + * Prepare our instructions to run the syscall. + * This is little-endian, so read bytes from right to left. + * We start with an INT3 to stop any other thread that might enter `main` + * while we're fiddling with it (highly unlikely). We rely on `processTrap` + * to resume them later on. Then we have the two bytes for the SYSCALL + * instruction and the rest is filled with NOPs. + */ + long newinsts = syscallInsts; + + /* Insert the SYSCALL instruction into the target */ + errno = 0; + if (ptrace(PTRACE_POKETEXT, traceePid, patchAddr, newinsts) < 0) { + LOG(ERROR) << "syscall: POKETEXT failed: " << strerror(errno); + return std::nullopt; + } + + { /* SINGLESTEP once to run the syscall */ + errno = 0; + if (ptrace(PTRACE_SINGLESTEP, traceePid, nullptr, nullptr) < 0) { + LOG(ERROR) << "syscall: SYSCALL " << Sys::Name + << " failed: " << strerror(errno); + return std::nullopt; + } + + int status = 0; + waitpid(traceePid, &status, 0); + + if (!WIFSTOPPED(status)) { + LOG(ERROR) << "process not stopped!"; + } + } + + /* Read the new register state, so we can get the syscall's return value */ + errno = 0; + if (ptrace(PTRACE_GETREGS, traceePid, nullptr, &newregs) < 0) { + LOG(ERROR) << "syscall: return GETREGS failed: " << strerror(errno); + return std::nullopt; + } + + if ((long long)newregs.rax < 0) { + LOG(ERROR) << "syscall: Syscall " << Sys::Name + << " failed with error: " << strerror((int)-newregs.rax); + return std::nullopt; + } + + typename Sys::RetType retval = (typename Sys::RetType)newregs.rax; + + VLOG(1) << "syscall " << Sys::Name << " returned " << std::hex << retval; + return retval; +} + +bool OIDebugger::setupSegment(SegType seg) { + Metrics::Tracing _("setup_segment"); + + std::optional segAddr; + if (seg == SegType::text) { + segAddr = + remoteSyscall(nullptr, textSegSize, // addr & size + PROT_READ | PROT_WRITE | PROT_EXEC, // prot + MAP_PRIVATE | MAP_ANONYMOUS, // flags + -1, 0); // fd & offset + } else { + segAddr = remoteSyscall(nullptr, dataSegSize, // addr & size + PROT_READ | PROT_WRITE, // prot + MAP_SHARED | MAP_ANONYMOUS, // flags + -1, 0); // fd & offset + } + + if (!segAddr.has_value()) { + return false; + } + + if (seg == SegType::text) { + segConfig.textSegBase = (uintptr_t)segAddr.value(); + segConfig.constStart = segConfig.textSegBase + prologueLength; + segConfig.jitCodeStart = segConfig.constStart + constLength; + segConfig.textSegSize = textSegSize; + + /* + * For the minute we'll use the last 512 bytes of text for storing + * replay instructions. + */ + segConfig.replayInstBase = + segConfig.textSegBase + textSegSize - replayInstSize; + VLOG(1) << "replayInstBase addr " << std::hex << segConfig.replayInstBase; + } else { + segConfig.dataSegBase = (uint64_t)*segAddr; + segConfig.dataSegSize = dataSegSize; + } + + return true; +} + +bool OIDebugger::unmapSegment(SegType seg) { + Metrics::Tracing _("unmap_segment"); + auto addr = + (seg == SegType::text) ? segConfig.textSegBase : segConfig.dataSegBase; + auto size = (seg == SegType::text) ? textSegSize : dataSegSize; + return remoteSyscall(addr, size).has_value(); +} + +bool OIDebugger::unmapSegments(bool deleteSegConfFile) { + bool ret = true; + + if (!unmapSegment(SegType::text)) { + LOG(ERROR) << "Problem unmapping target process text segment"; + ret = false; + } + + if (ret && !unmapSegment(SegType::data)) { + LOG(ERROR) << "Problem unmapping target process data segment"; + ret = false; + } + + if (ret && !cleanupLogFile()) { + LOG(ERROR) << "Problem closing target process log file"; + ret = false; + } + + deleteSegmentConfig(deleteSegConfFile); + + return ret; +} + +/* + * The debugger is shutting things down so all breakpoint traps need to be + * removed from the target process. As oid is currently single threaded + * there isn't a whole lot to race over so its pretty easy. We just need + * to be careful that no target threads end up waiting for signals to be + * be dealt with by us. + * + * NOTE: This code can be called from a signal handler context so keep it + * async-signal-safe. Obviously any output in debug or error modes are not + * async-signal-safe but we'll live with that for now. + * + * The calling thread *must* be stopped before calling this interface. + * Unfortunately there is no cheap way to assert this. + */ +bool OIDebugger::removeTraps(pid_t pid) { + Metrics::Tracing removeTrapsTracing("remove_traps"); + + pid_t targetPid = pid ? pid : traceePid; + + /* Hijack the main thread to remove the traps and flush the JIT logs */ + errno = 0; + if (ptrace(PTRACE_INTERRUPT, targetPid, nullptr, nullptr) < 0) { + LOG(ERROR) << "Couldn't interrupt target pid " << targetPid << ": " + << strerror(errno); + return false; + } + + errno = 0; + if (waitpid(targetPid, 0, WSTOPPED) != targetPid) { + LOG(ERROR) << "Failed to stop the target pid " << targetPid << ": " + << strerror(errno); + } + + for (auto it = activeTraps.begin(); it != activeTraps.end();) { + const auto &tInfo = it->second; + + /* We don't care about our own traps */ + if (tInfo->trapKind == OID_TRAP_JITCODERET) { + ++it; + continue; + } + + VLOG(1) << "removeTraps removing int3 at " << std::hex << tInfo->trapAddr; + + errno = 0; + if (ptrace(PTRACE_POKETEXT, targetPid, tInfo->trapAddr, tInfo->origText) < + 0) { + LOG(ERROR) << "Execute: Couldn't poke text: " << strerror(errno); + } + + it = activeTraps.erase(it); + } + + if (enableJitLogging) { + /* Flush the JIT log, so it's always written on disk at least once */ + if (!remoteSyscall(segConfig.logFile).has_value()) { + LOG(ERROR) << "Failed to flush the JIT Log"; + } + } + + /* Resume the main thread now, so it doesn't have to wait on restoreState */ + errno = 0; + if (ptrace(PTRACE_CONT, targetPid, nullptr, nullptr) < 0) { + LOG(ERROR) << "Couldn't continue target pid " << targetPid + << " (Reason: " << strerror(errno) << ")"; + return false; + } + + return true; +} + +bool OIDebugger::removeTrap(pid_t pid, const trapInfo &t) { + std::array repatchedBytes{}; + memcpy(repatchedBytes.data(), t.origTextBytes, repatchedBytes.size()); + + if (auto it = activeTraps.find(t.trapAddr); it != end(activeTraps)) { + /* + * `ptrace(POKETEXT)` writes 8 bytes in the target process. If multiple + * traps are close to each others, the 8 bytes window might overlap with + * multiple of them. We iter on the items that are within this window and + * copy their patched bytes over ensuring we don't lose their INT3. + */ + constexpr auto windowSize = repatchedBytes.size(); + while (++it != end(activeTraps)) { + uintptr_t off = it->first - t.trapAddr; + if (off >= windowSize) { + break; + } + + memcpy(repatchedBytes.data() + off, it->second->patchedTextBytes, + windowSize - off); + } + } + + VLOG(1) << "removeTrap removing int3 at " << std::hex << t.trapAddr; + if (ptrace(PTRACE_POKETEXT, (!pid ? traceePid : pid), t.trapAddr, + *reinterpret_cast(repatchedBytes.data())) < 0) { + LOG(ERROR) << "Execute: Couldn't poke text: " << strerror(errno); + return false; + } + + activeTraps.erase(t.trapAddr); + + return true; +} + +OIDebugger::OIDebugger(std::string configFile, OICodeGen::Config genConfig, + TreeBuilder::Config tbConfig) + : configFilePath{configFile}, + generatorConfig{std::move(genConfig)}, + treeBuilderConfig{std::move(tbConfig)} { + if (configFilePath.empty()) { + configFilePath = fs::current_path() / "oid.toml"; + } + + VLOG(1) << "config file: " << configFilePath; + + debug = true; + OIUtils::processConfigFile(configFilePath, compilerConfig, generatorConfig); + + cache.generatorConfig = generatorConfig; + VLOG(1) << "CodeGen config: " << generatorConfig.toString(); +} + +OIDebugger::OIDebugger(pid_t pid, std::string configFile, + OICodeGen::Config genConfig, + TreeBuilder::Config tbConfig) + : OIDebugger(std::move(configFile), std::move(genConfig), + std::move(tbConfig)) { + traceePid = pid; + symbols = std::make_shared(traceePid); + setDataSegmentSize(dataSegSize); + createSegmentConfigFile(); + cache.symbols = symbols; +} + +OIDebugger::OIDebugger(fs::path debugInfo, std::string configFile, + OICodeGen::Config genConfig, + TreeBuilder::Config tbConfig) + : OIDebugger(std::move(configFile), std::move(genConfig), + std::move(tbConfig)) { + symbols = std::make_shared(std::move(debugInfo)); + cache.symbols = symbols; +} + +/* + * Copy local buffer from specified address into the remote address in + * the target process. Note that this only works for mappings that have + * write permissions set (i.e., mappings that we have created and not + * standard text mappings). + * + * @param[in] local_buffer - buffer containing data to be written + * @param[in] target_addr - target address where new data are to be written + * @param[in] bufsz - length of 'target_addr' buffer in bytes + */ +bool OIDebugger::writeTargetMemory(void *local_buffer, void *target_addr, + size_t bufsz) const { + VLOG(1) << "Writting buffer " << std::hex << local_buffer << ", bufsz " + << std::dec << bufsz << " into target " << std::hex << target_addr; + + struct iovec liovecs[] = {{ + .iov_base = local_buffer, + .iov_len = bufsz, + }}; + size_t liovcnt = sizeof(liovecs) / sizeof(struct iovec); + + struct iovec riovecs[] = {{ + .iov_base = target_addr, + .iov_len = bufsz, + }}; + size_t riovcnt = sizeof(riovecs) / sizeof(struct iovec); + + auto writtenBytesCount = + process_vm_writev(traceePid, liovecs, liovcnt, riovecs, riovcnt, 0); + if (writtenBytesCount == -1) { + LOG(ERROR) << "process_vm_writev() error: " << strerror(errno) << " (" + << errno << ")"; + return false; + } + + if (static_cast(writtenBytesCount) != bufsz) { + LOG(ERROR) << "process_vm_writev() wrote only " << writtenBytesCount + << " bytes, expected " << bufsz << " bytes"; + return false; + } + + return true; +} + +/* + * Copy remote buffer from specified address in the target process into + * the local address. + * + * @param[in] remote_buffer - buffer in the target process where the data is + * read from + * @param[in] local_addr - local address where new data are to be written + * @param[in] bufsz - length of 'local_addr' buffer in bytes + */ +bool OIDebugger::readTargetMemory(void *remote_buffer, void *local_addr, + size_t bufsz) const { + VLOG(1) << "Reading buffer " << std::hex << remote_buffer << ", bufsz " + << std::dec << bufsz << " into local " << std::hex << local_addr; + + struct iovec liovecs[] = {{ + .iov_base = local_addr, + .iov_len = bufsz, + }}; + size_t liovcnt = sizeof(liovecs) / sizeof(struct iovec); + + struct iovec riovecs[] = {{ + .iov_base = remote_buffer, + .iov_len = bufsz, + }}; + size_t riovcnt = sizeof(riovecs) / sizeof(struct iovec); + + auto readBytesCount = + process_vm_readv(traceePid, liovecs, liovcnt, riovecs, riovcnt, 0); + if (readBytesCount == -1) { + LOG(ERROR) << "process_vm_readv() error: " << strerror(errno) << " (" + << errno << ")"; + return false; + } + + if (static_cast(readBytesCount) != bufsz) { + LOG(ERROR) << "process_vm_readv() wrote only " << readBytesCount + << " bytes, expected " << bufsz << " bytes"; + return false; + } + + return true; +} + +std::optional> +OIDebugger::locateJitCodeStart( + const irequest &req, const OICompiler::RelocResult::SymTable &jitSymbols) { + // Get type of probed object to locate the JIT code start + OIDebugger::ObjectAddrMap::key_type targetObj; + if (req.type == "global") { + const auto &gd = symbols->findGlobalDesc(req.func); + if (!gd) { + LOG(ERROR) << "Failed to find GlobalDesc for " << req.func; + return std::nullopt; + } + + targetObj = gd; + } else { + const auto &fd = symbols->findFuncDesc(req); + if (!fd) { + LOG(ERROR) << "Failed to find FuncDesc for " << req.func; + return std::nullopt; + } + + const auto &farg = fd->getArgument(req.arg); + if (!farg) { + LOG(ERROR) << "Failed to get argument for " << req.func << ':' << req.arg; + return std::nullopt; + } + + targetObj = farg; + } + + auto &typeName = std::visit( + [](auto &&obj) -> std::string & { return obj->typeName; }, targetObj); + auto typeHash = std::hash{}(typeName); + auto jitCodeName = (boost::format("_Z24getSize_%016x") % typeHash).str(); + + uintptr_t jitCodeStart = 0; + for (const auto &[symName, symAddr] : jitSymbols) { + if (symName.starts_with(jitCodeName)) { + jitCodeStart = symAddr; + break; + } + } + + if (jitCodeStart == 0) { + LOG(ERROR) << "Couldn't find " << jitCodeName << " in symbol table"; + return std::nullopt; + } + + return std::make_pair(std::move(targetObj), jitCodeStart); +} + +/* + * Hmmm. I'm pretty sure the JIT'd code sequence is always going to be a + * non-leaf function at the top level and therefore it's going to finish + * with a 'ret'. This means that we're going to need to get into it + * with a 'call' (obviously!). I want to keep the generated code all + * contained within the mmap'd text area and therefore I need to manually + * craft a call sequence which will do the following: + * + * movabs addr_of_object, %rdi + * movabs addr_of_entry_top_level_func, %rax + * call (*rax) + * movabs regs.rip, %rdi + * jmpq *(%rdi) + * + * Maybe we can do better than this shenanigans but not sure how at the + * minute. This above sequence will go as a 'prologue' at the start of the + * mmaped section, before our JIT'd sequence and this will affect the base + * address of the relocations. NOTE: be very careful that we pass in an + * address to setBaseRelocAddr() above that takes into account the prologue + * sequence we are constructing here otherwise the relocations will be + * wrong. Assume the prologue is 64 bytes. + * + * Note that the movabs is a whopper of an instruction at 10 bytes. I'm + * sure I could do it with less if I need to. Absolute addressing keeps + * things simple though. + * + * A note on rounding. The ptrace peek and poke commands deal with word + * size operations and this means that we need to round up to a word + * boundary for our instruction buffers. Therefore we need to pad out the + * extra space in our new text buffer with NOP instructions to avoid side + * effects. + */ + +bool OIDebugger::writePrologue( + const prequest &preq, const OICompiler::RelocResult::SymTable &jitSymbols) { + size_t off = 0; + uint8_t newInsts[prologueLength]; + memset(newInsts, nopInst /* NOP */, sizeof(newInsts)); + + /* + * Global probes don't have multiple arguments, but calling `getReqForArg(X)` + * on them still returns the corresponding irequest. We take advantage of that + * to re-use the same code to generate prologue for both global and func + * probes. + */ + size_t argCount = preq.type == "global" ? 1 : preq.args.size(); + + for (size_t i = 0; i < argCount; i++) { + const auto &req = preq.getReqForArg(i); + + auto jitCodeStart = locateJitCodeStart(req, jitSymbols); + if (!jitCodeStart.has_value()) { + LOG(ERROR) << "Failed to locate JIT code start for " << req.func << ':' + << req.arg; + return false; + } + + VLOG(1) << "Generating prologue for argument '" << req.arg + << "', using probe at " << (void *)jitCodeStart->second; + + /* + * With the move to an INT3 to regain control of the target thread I'm + * not convinced that we actually need to do any of this now. We may be + * able to simply tack an INT3 on to the end of the JIT'd code sequence + * (obviously we wouldn't ever execute the 'ret' there but that doesn't + * really matter). + */ + /* + * movabs is really a synthetic for a REX prefixed mov instruction. + * The REX prefix opcode is 0x48 (REX.W == 1). + */ + newInsts[off++] = movabsrdi0Inst; + newInsts[off++] = movabsrdi1Inst; + remoteObjAddrs.emplace(std::move(jitCodeStart->first), + segConfig.textSegBase + off); + std::visit([](auto &&obj) { obj = nullptr; }, + jitCodeStart->first); // Invalidate ptr after move + memcpy(newInsts + off, &objectAddr, sizeof(objectAddr)); + off += sizeof(objectAddr); + + newInsts[off++] = movabsrax0Inst; + newInsts[off++] = movabsrax1Inst; + memcpy(newInsts + off, &jitCodeStart->second, sizeof(jitCodeStart->second)); + off += sizeof(jitCodeStart->second); + + newInsts[off++] = callRaxInst0Inst; + newInsts[off++] = callRaxInst1Inst; + } + + VLOG(1) << "INT3 at offset " << std::hex << off; + + auto t = std::make_shared(OID_TRAP_JITCODERET, + segConfig.textSegBase + off); + auto ret = activeTraps.emplace(t->trapAddr, t); + if (ret.second == false) { + LOG(ERROR) << "activeTrap element for 0x" << t->trapAddr + << " already exists (writePrologue error!)"; + } + + newInsts[off++] = int3Inst; + + assert(off <= prologueLength); + + return writeTargetMemory(&newInsts, (void *)segConfig.textSegBase, + prologueLength); +} + +/* + * Compile the code that the OICompiler layer knows about. The result of this + * is that the target processes text segment is populated and ready to go. + */ +bool OIDebugger::compileCode() { + assert(pdata.numReqs() == 1); + const auto &preq = pdata.getReq(); + + OICompiler compiler{symbols, compilerConfig}; + std::set objectFiles{}; + + /* + * Global probes don't have multiple arguments, but calling `getReqForArg(X)` + * on them still returns the corresponding irequest. We take advantage of that + * to re-use the same code to generate prologue for both global and func + * probes. + */ + size_t argCount = preq.type == "global" ? 1 : preq.args.size(); + for (size_t i = 0; i < argCount; i++) { + const auto &req = preq.getReqForArg(i); + + if (cache.isEnabled()) { + // try to download cache artifacts if present + if (!downloadCache()) { +#ifndef OSS_ENABLE + // Send a request to the GOBS service + char buf[PATH_MAX]; + const std::string procpath = + "/proc/" + std::to_string(traceePid) + "/exe"; + size_t buf_size; + if ((buf_size = readlink(procpath.c_str(), buf, PATH_MAX)) == -1) { + LOG(ERROR) + << "Failed to get binary path for tracee, could not call GOBS: " + << strerror(errno); + } else { + LOG(INFO) << "Attempting to get cache request from gobs"; + ObjectIntrospection::GobsService::requestCache( + procpath, std::string(buf, buf_size), req.toString(), + generatorConfig.toOptions()); + } +#endif + LOG(ERROR) << "No cache file found, exiting!"; + return false; + } + + if (req.type == "global") { + decltype(symbols->globalDescs) gds; + if (cache.load(req, OICache::Entity::GlobalDescs, gds)) { + symbols->globalDescs.merge(std::move(gds)); + } + } else { + decltype(symbols->funcDescs) fds; + if (cache.load(req, OICache::Entity::FuncDescs, fds)) { + symbols->funcDescs.merge(std::move(fds)); + } + } + } + + auto sourcePath = cache.getPath(req, OICache::Entity::Source); + auto objectPath = cache.getPath(req, OICache::Entity::Object); + auto typeHierarchyPath = cache.getPath(req, OICache::Entity::TypeHierarchy); + auto paddingInfoPath = cache.getPath(req, OICache::Entity::PaddingInfo); + + if (!sourcePath || !objectPath || !typeHierarchyPath || !paddingInfoPath) { + LOG(ERROR) << "Failed to get all cache paths, aborting!"; + return false; + } + + bool skipCodeGen = cache.isEnabled() && fs::exists(*objectPath) && + fs::exists(*typeHierarchyPath) && + fs::exists(*paddingInfoPath); + if (skipCodeGen) { + std::pair th; + skipCodeGen = + skipCodeGen && cache.load(req, OICache::Entity::TypeHierarchy, th); + + std::map pad; + skipCodeGen = + skipCodeGen && cache.load(req, OICache::Entity::PaddingInfo, pad); + + if (skipCodeGen) { + typeInfos.emplace(req, std::make_tuple(th.first, th.second, pad)); + } + } + + if (!skipCodeGen) { + VLOG(2) << "Compiling probe for '" << req.arg + << "' into: " << *objectPath; + + auto code = generateCode(req); + if (!code.has_value()) { + LOG(ERROR) << "Failed to generate code"; + return false; + } + + bool doCompile = !cache.isEnabled() || !fs::exists(*objectPath); + if (doCompile) { + if (!compiler.compile(*code, *sourcePath, *objectPath)) { + LOG(ERROR) << "Failed to compile code"; + return false; + } + } + } + + if (cache.isEnabled() && !skipCodeGen) { + if (req.type == "global") { + cache.store(req, OICache::Entity::GlobalDescs, symbols->globalDescs); + } else { + cache.store(req, OICache::Entity::FuncDescs, symbols->funcDescs); + } + + const auto &[rootType, typeHierarchy, paddingInfo] = typeInfos.at(req); + cache.store(req, OICache::Entity::TypeHierarchy, + std::make_pair(rootType, typeHierarchy)); + cache.store(req, OICache::Entity::PaddingInfo, paddingInfo); + } + + objectFiles.insert(*objectPath); + } + + if (traceePid) { // we attach to a process + std::unordered_map syntheticSymbols{ + {"dataBase", segConfig.constStart + 0 * sizeof(uintptr_t)}, + {"dataSize", segConfig.constStart + 1 * sizeof(uintptr_t)}, + {"cookieValue", segConfig.constStart + 2 * sizeof(uintptr_t)}, + {"logFile", segConfig.constStart + 3 * sizeof(uintptr_t)}, + }; + + VLOG(2) << "Relocating..."; + for (const auto &o : objectFiles) { + VLOG(2) << " * " << o; + } + auto relocRes = compiler.applyRelocs(segConfig.jitCodeStart, objectFiles, + syntheticSymbols); + if (!relocRes.has_value()) { + LOG(ERROR) << "Failed to relocate object code"; + return false; + } + + const auto &[_, segments, jitSymbols] = relocRes.value(); + for (const auto &[symName, symAddr] : jitSymbols) { + VLOG(2) << "sym " << symName << '@' << std::hex << symAddr; + } + + const auto &lastSeg = segments.back(); + auto segmentsLimit = lastSeg.RelocAddr + lastSeg.Size; + auto remoteSegmentLimit = segConfig.textSegBase + segConfig.textSegSize; + if (segmentsLimit > remoteSegmentLimit) { + size_t totalSegmentsSize = segmentsLimit - segConfig.textSegBase; + LOG(ERROR) << "Generated instruction sequence too large for currently " + "mapped text segment. Instruction size: " + << totalSegmentsSize << " text mapping size: " << textSegSize; + return false; + } + + for (const auto &[BaseAddr, RelocAddr, Size] : segments) { + if (!writeTargetMemory((void *)BaseAddr, (void *)RelocAddr, Size)) { + return false; + } + } + + if (!writeTargetMemory(&segConfig.dataSegBase, + (void *)syntheticSymbols["dataBase"], + sizeof(segConfig.dataSegBase))) { + LOG(ERROR) << "Failed to write dataSegBase in probe's dataBase"; + return false; + } + + if (!writeTargetMemory(&dataSegSize, (void *)syntheticSymbols["dataSize"], + sizeof(dataSegSize))) { + LOG(ERROR) << "Failed to write dataSegSize in probe's dataSize"; + return false; + } + + if (!writeTargetMemory(&segConfig.cookie, + (void *)syntheticSymbols["cookieValue"], + sizeof(segConfig.cookie))) { + LOG(ERROR) << "Failed to write cookie in probe's cookieValue"; + return false; + } + + int logFile = enableJitLogging ? segConfig.logFile : 0; + if (!writeTargetMemory(&logFile, (void *)syntheticSymbols["logFile"], + sizeof(logFile))) { + LOG(ERROR) << "Failed to write logFile in probe's cookieValue"; + return false; + } + + if (!writePrologue(preq, jitSymbols)) { + LOG(ERROR) << "Failed to write prologue"; + return false; + } + } + + return true; +} + +/* TODO: Needs some cleanup and generally making more resilient */ +void OIDebugger::restoreState(void) { + /* + * We are about to detach from the target process. + * Ensure we don't have any trap in the target process still active. + */ + const size_t activeTrapsCount = std::count_if( + activeTraps.cbegin(), activeTraps.cend(), + [](const auto &t) { return t.second->trapKind != OID_TRAP_JITCODERET; }); + VLOG(1) << "Active traps still within the target process: " + << activeTrapsCount; + assert(activeTrapsCount == 0); + + Metrics::Tracing _("restore_state"); + + int status = 0; + + /* + * (1) Check if the thread is already stopped (because it hit a SIGTRAP), + * (2) If thread is not already stopped, interrupt it so that we can detach + * from it, + * (3) If the thread was already stopped because of a SIGTRAP, reset register + * state to that of the thread when it first came under oid control. The + * thread could still be in oid JIT code here or trapping in from it normal + * execution path. + */ + for (auto const &p : threadList) { + auto state = getTaskState(p); + VLOG(1) << "Task " << p << " state: " << taskStateToString(state) << " (" + << static_cast(state) << ")"; + pid_t ret = waitpid(p, &status, WNOHANG | WSTOPPED); + if (ret < 0) { + LOG(ERROR) << "Error in waitpid (pid " << p << ")" << strerror(errno); + } else if (ret == 0) { + if (WIFSTOPPED(status)) { + VLOG(1) << "Process " << p << " signal-delivery-stopped " + << WSTOPSIG(status); + } else { + VLOG(1) << "Process " << p << " not signal-delivery-stopped state"; + } + + if (WIFSIGNALED(status)) { + VLOG(1) << "Process " << p << " terminated with signal " + << WTERMSIG(status); + } else { + VLOG(1) << "Process " << p << " WIFSIGNALED is false "; + } + VLOG(1) << "Stopping PID : " << p; + + if (ptrace(PTRACE_INTERRUPT, p, NULL, NULL) < 0) { + VLOG(1) << "Couldn't interrupt target pid " << p + << " (Reason: " << strerror(errno) << ")"; + } + + VLOG(1) << "Waiting to stop PID : " << p; + + if (waitpid(p, 0, WSTOPPED) != p) { + LOG(ERROR) << "failed to wait for process " << p + << " (Reason: " << strerror(errno) << ")"; + } + + VLOG(1) << "Stopped PID : " << p; + } else if (WSTOPSIG(status) == SIGTRAP) { + VLOG(1) << "Thread already stopped PID : " << p << " signal is " + << WSTOPSIG(status); + + /* + * PTRACE_EVENT stop. As noted previously, not sure if this is + * the correct way for testing for the existence of a ptrace event + * stop but it seems to work. + */ + if (status >> 16) { + int type = (status >> 16); + + if (type == PTRACE_EVENT_CLONE || type == PTRACE_EVENT_FORK || + type == PTRACE_EVENT_VFORK) { + /* + * Detach the new child as it appears to be under our control + * at this point in its life. + */ + unsigned long childPid = 0; + ptrace(PTRACE_GETEVENTMSG, p, NULL, &childPid); + + VLOG(1) << "New child being created!! pid " << std::dec << childPid; + + if (ptrace(PTRACE_DETACH, childPid, 0L, 0L) < 0) { + LOG(ERROR) << "Couldn't detach target pid " << childPid + << " (Reason: " << strerror(errno) << ")"; + } else { + VLOG(1) << "Successfully detached from pid " << childPid; + } + } + + if (ptrace(PTRACE_DETACH, p, 0L, 0L) < 0) { + LOG(ERROR) << "Couldn't detach target pid " << p + << " (Reason: " << strerror(errno) << ")"; + } else { + VLOG(1) << "Successfully detached from pid " << p; + } + + continue; + } + + struct user_regs_struct regs {}; + + /* Find the trapInfo for this tgid */ + if (auto iter{threadTrapState.find(p)}; + iter != std::end(threadTrapState)) { + auto t{iter->second}; + + /* Paranoia really */ + assert(p == iter->first); + + struct user_fpregs_struct fpregs {}; + + if (VLOG_IS_ON(1)) { + errno = 0; + if (ptrace(PTRACE_GETREGS, p, NULL, ®s) < 0) { + LOG(ERROR) << "restoreState failed to read registers: " + << strerror(errno); + } + dumpRegs("Before1", p, ®s); + } + + memcpy((void *)®s, (void *)&t->savedRegs, sizeof(regs)); + memcpy((void *)&fpregs, (void *)&t->savedFPregs, sizeof(fpregs)); + + /* + * Note that we need to rewind the original %rip as it has trapped + * on an INT3 (which has now been replaced by the original + * instruction. + */ + regs.rip -= sizeofInt3; + + errno = 0; + if (ptrace(PTRACE_SETREGS, p, NULL, ®s) < 0) { + LOG(ERROR) << "restoreState: Couldn't restore registers: " + << strerror(errno); + } + dumpRegs("After1", p, ®s); + + errno = 0; + if (ptrace(PTRACE_SETFPREGS, p, NULL, &fpregs) < 0) { + LOG(ERROR) << "restorState: Couldn't restore fp registers: " + << strerror(errno); + } + + VLOG(1) << "Set registers for pid " << std::dec << iter->first; + } else { + /* + * If no trapinfo exists for this thread then it must have just trapped + * as a result of hitting a traced function and not JIT code. As so, + * just rewind the %rip register to back it up and let it continue. + * We're safe to just back it up as the original instructions have + * been replaced at this point. + */ + VLOG(1) << "Couldn't find trapInfo for pid " << std::dec << p; + + errno = 0; + if (ptrace(PTRACE_GETREGS, p, NULL, ®s) < 0) { + LOG(ERROR) << "restoreState SIGTRAP handling: getregs failed - " + << strerror(errno); + } + + dumpRegs("Before2", p, ®s); + regs.rip -= sizeofInt3; + dumpRegs("After2", p, ®s); + + errno = 0; + if (ptrace(PTRACE_SETREGS, p, NULL, ®s) < 0) { + LOG(ERROR) << "restoreState SIGTRAP handling: setregs failed - " + << strerror(errno); + } + VLOG(1) << "Set registers for thread " << std::dec << p; + } + } else { + LOG(WARNING) << "Thread " << p + << " stopped with unknown signal : " << WSTOPSIG(status) + << "ret: " << ret << " state: " << taskStateToString(state) + << "(" << static_cast(state) << ")"; + + if (state == StatusType::zombie || state == StatusType::dead) { + /* + * Pretty sure we don't need to do anything else if the thread + * is a zombie or dead. + */ + continue; + } + + if (VLOG_IS_ON(1)) { + errno = 0; + struct user_regs_struct regs {}; + if (ptrace(PTRACE_GETREGS, p, NULL, ®s) < 0) { + LOG(ERROR) << "restoreState unknown sig handling: getregs failed- " + << strerror(errno); + } + dumpRegs("Unknown Sig", p, ®s); + } + } + + if (ptrace(PTRACE_DETACH, p, 0L, 0L) < 0) { + LOG(ERROR) << "restoreState Couldn't detach target pid " << p + << " (Reason: " << strerror(errno) << ")"; + } else { + VLOG(1) << "Successfully detached from pid " << p; + } + } +} + +bool OIDebugger::targetAttach() { + /* + * ptrace sucks. It doesn't have a mechanism for grabbing all the threads in + * a process (even if it's a SEIZE operation). Grabbing all threads seems to + * be a huge race condition so unless we find a better way to do it, we need + * to be as defensive as we can. + */ + if (mode == OID_MODE_THREAD) { + if (ptrace(PTRACE_ATTACH, traceePid, NULL, NULL) < 0) { + LOG(ERROR) << "Couldn't attach to target pid " << traceePid + << " (Reason: " << strerror(errno) << ")"; + return false; + } + std::cout << "Attached to pid " << traceePid << std::endl; + } else { + /* TODO - Handle exceptions */ + auto pidPath = fs::path("/proc") / std::to_string(traceePid) / "task"; + try { + for (const auto &entry : fs::directory_iterator(pidPath)) { + auto file = entry.path().filename().string(); + auto pid = std::stoi(file); + + VLOG(1) << "Seizing thread: " << pid; + + /* + * As mentioned above, this code is terribly racy and we need a better + * solution. If the seize operation fails we will carry on and assume + * that the thread has exited in between readinf the directory and + * here (note: ptrace(2) overloads the ESRCH return but with a seize + * I think it can only mean one thing). + */ + if (ptrace(PTRACE_SEIZE, pid, NULL, + PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | + PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXIT) < 0) { + LOG(ERROR) << "Couldn't seize thread " << pid + << " (Reason: " << strerror(errno) << ")"; + } else { + threadList.push_back(pid); + } + } + } catch (std::filesystem::filesystem_error const &ex) { + LOG(ERROR) << "directory_iterator exception: " << ex.path1() + << ex.code().message(); + + /* + * As we haven't interrupted any threads yet, any threads which have + * been seized should be released explicitly when this process exits. + */ + return false; + } + } + + return true; +} + +bool OIDebugger::stopTarget(void) { + assert(traceePid > 0); + + /* + * Note: PTRACE_ATTACH sends a SIGSTOP to the traced process but PTRACE_SEIZE + * doesn't do this. I think that both of these interfaces will still end up + * with the tracee reparented to us for the duration of the trace session + * though. To interrupt a traced process if PTRACE_SEIZE has been used just + * use PTRACE_INTERRUPT (this means no signals are delivered which seems + * like goodness to me). + */ + + /* + * If we are in OID_MODE_FUNC then all threads should have been seized + * but now but we are only going to stop the main target (as supplied to us). + * This will be the thread that we will use to set everything up. I would + * kill for an lwp-agent thread. + */ + if (!targetAttach()) { + return false; + } + + if (mode == OID_MODE_FUNC) { + if (VLOG_IS_ON(1)) { + OIDebugger::StatusType s = getTaskState(traceePid); + VLOG(1) << "stopTarget: Interrupting pid " << traceePid + << " state: " << static_cast(s); + } else { + VLOG(1) << "stopTarget: Interrupting pid " << traceePid; + } + + if (ptrace(PTRACE_INTERRUPT, traceePid, NULL, NULL) < 0) { + LOG(ERROR) << "Couldn't interrupt target pid " << traceePid + << " (Reason: " << strerror(errno) << ")"; + return false; + } + } + + /* + * The PTRACE_ATTACH/INTERRUPT cmds aren't synchronous so the tracee may + * not be stopped by the time we are here. Wait for it to actually stop. + */ + + if (waitpid(traceePid, 0, WSTOPPED) != traceePid) { + LOG(ERROR) << "stopTarget failed to wait for process " << traceePid + << " (Reason: " << strerror(errno) << ")"; + + if (ptrace(PTRACE_DETACH, traceePid, NULL, NULL) < 0) { + LOG(ERROR) << "stopTarget failed to detach from process " << traceePid + << "before exiting"; + } + + return false; + } + + if (VLOG_IS_ON(1)) { + errno = 0; + struct user_regs_struct stopregs {}; + if (ptrace(PTRACE_GETREGS, traceePid, NULL, &stopregs) < 0) { + LOG(ERROR) << "stopTarget getregs failed for process " << traceePid + << " : " << strerror(errno); + + return false; + } + + dumpRegs("stopregs: ", traceePid, &stopregs); + } + + return true; +} + +/* + * Used by SIGINT handler to close down debugging of the target as the + * debugger must exit. This means that all tracing must be removed from the + * target when it is safe to do so. + */ +void OIDebugger::stopAll() { + oidShouldExit = true; +} + +void OIDebugger::setDataSegmentSize(size_t size) { + /* round up to the next page boundary if not aligned */ + int pgsz = getpagesize(); + dataSegSize = ((size + pgsz - 1) & ~(pgsz - 1)); + + generatorConfig.useDataSegment = dataSegSize > 0; + + VLOG(1) << "setDataSegmentSize: segment size: " << dataSegSize; +} + +bool OIDebugger::decodeTargetData(const DataHeader &dataHeader, + std::vector &outVec) const { + VLOG(1) << "== magicId: " << std::hex << dataHeader.magicId; + VLOG(1) << "== cookie: " << std::hex << dataHeader.cookie; + VLOG(1) << "== size: " << dataHeader.size; + + if (dataHeader.magicId != oidMagicId) { + LOG(ERROR) << "Got a wrong magic ID: " << std::hex << dataHeader.magicId; + return false; + } + + if (dataHeader.cookie != segConfig.cookie) { + LOG(ERROR) << "Got a wrong cookie: " << std::hex << dataHeader.cookie; + return false; + } + + VLOG(1) << "Total bytes in data segment " << dataHeader.size; + if (dataHeader.size == 0) { + LOG(ERROR) + << "Data segment is empty. Something went wrong while probing..."; + return false; + } + + if (dataSegSize < dataHeader.size) { + LOG(ERROR) << "Error: Data segment is too small. Needed: " + << dataHeader.size << " bytes, dataseg size " << dataSegSize + << " bytes"; + return false; + } + + /* + * Currently we use MAX_INT to indicate two things: + * - a single MAX_INT indicates the end of results for the current object + * - two consecutive MAX_INT's indicate we have finished completely. + */ + folly::ByteRange range(dataHeader.data, dataHeader.size - sizeof(dataHeader)); + + outVec.push_back(0); + outVec.push_back(0); + outVec.push_back(0); + uint64_t prevVal = 0; + + while (true) { + /* XXX Sort out the sentinel value!!! */ + auto expected = tryDecodeVarint(range); + if (!expected) { + std::string s = + (expected.error() == folly::DecodeVarintError::TooManyBytes) + ? "Invalid varint value: too many bytes." + : "Invalid varint value: too few bytes."; + LOG(ERROR) << s; + return false; + } + uint64_t currVal = expected.value(); + + if (currVal == 123456789) { + if (prevVal == 123456789) { + break; + } + } else { + outVec.push_back(currVal); + } + prevVal = currVal; + } + + return true; +} + +static bool dumpDataSegment(const irequest &req, + const std::vector &dataSeg) { + char dumpPath[PATH_MAX] = {0}; + auto dumpPathSize = + snprintf(dumpPath, sizeof(dumpPath), "/tmp/dataseg.%d.%s.dump", getpid(), + req.arg.c_str()); + if (dumpPathSize < 0 || (size_t)dumpPathSize > sizeof(dumpPath)) { + LOG(ERROR) << "Failed to generate data-segment path"; + return false; + } + + std::ofstream dumpFile{dumpPath, std::ios_base::binary}; + if (!dumpFile) { + LOG(ERROR) << "Failed to open data-segment file '" << dumpPath + << "': " << strerror(errno); + return false; + } + + const auto outVecBytes = std::as_bytes(std::span{dataSeg}); + dumpFile.write((const char *)outVecBytes.data(), outVecBytes.size()); + if (!dumpFile) { + LOG(ERROR) << "Failed to write to data-segment file '" << dumpPath + << "': " << strerror(errno); + return false; + } + + return true; +} + +bool OIDebugger::processTargetData() { + Metrics::Tracing _("process_target_data"); + + std::vector buf{dataSegSize}; + if (!readTargetMemory(reinterpret_cast(segConfig.dataSegBase), + buf.data(), dataSegSize)) { + LOG(ERROR) << "Failed to read data segment from target process"; + return false; + } + + auto res = reinterpret_cast(buf.data()); + + assert(pdata.numReqs() == 1); + const auto &preq = pdata.getReq(); + + PaddingHunter paddingHunter{}; + TreeBuilder typeTree(treeBuilderConfig); + + /* + * Global probes don't have multiple arguments, but calling `getReqForArg(X)` + * on them still returns the corresponding irequest. We take advantage of that + * to re-use the same code to generate prologue for both global and func + * probes. + */ + size_t argCount = preq.type == "global" ? 1 : preq.args.size(); + + std::vector outVec{}; + for (size_t i = 0; i < argCount; i++) { + const auto &req = preq.getReqForArg(i); + LOG(INFO) << "Processing data for argument: " << req.arg; + + const auto &dataHeader = *reinterpret_cast(res); + res += dataHeader.size; + + outVec.clear(); + if (!decodeTargetData(dataHeader, outVec)) { + LOG(ERROR) << "Failed to decode target data for arg: " << req.arg; + return false; + } + + if (treeBuilderConfig.dumpDataSegment) { + if (!dumpDataSegment(req, outVec)) { + LOG(ERROR) << "Failed to dump data-segment for " << req.arg; + } + } + + auto typeInfo = typeInfos.find(req); + if (typeInfo == end(typeInfos)) { + LOG(ERROR) << "Failed to find corresponding typeInfo for arg: " + << req.arg; + return false; + } + + const auto &[rootType, typeHierarchy, paddingInfos] = typeInfo->second; + VLOG(1) << "Root type addr: " << (void *)rootType.type.type; + + if (treeBuilderConfig.genPaddingStats) { + paddingHunter.localPaddedStructs = paddingInfos; + typeTree.setPaddedStructs(&paddingHunter.localPaddedStructs); + } + + try { + typeTree.build(outVec, rootType.varName, rootType.type.type, + typeHierarchy); + } catch (std::exception &e) { + LOG(ERROR) << "Failed to run TreeBuilder for " << req.arg; + LOG(ERROR) << e.what(); + + if (treeBuilderConfig.dumpDataSegment) { + LOG(ERROR) << "Data-segment has been dumped for " << req.arg; + } else { + LOG(ERROR) << "Dumping data-segment for " << req.arg; + if (!dumpDataSegment(req, outVec)) { + LOG(ERROR) << "Failed to dump data-segment for " << req.arg; + } + } + + continue; + } + + if (treeBuilderConfig.genPaddingStats) { + paddingHunter.processLocalPaddingInfo(); + } + } + + if (typeTree.emptyOutput()) { + LOG(FATAL) + << "Nothing to output: failed to run TreeBuilder on any argument"; + } + + if (treeBuilderConfig.jsonPath.has_value()) { + typeTree.dumpJson(); + } + + if (treeBuilderConfig.genPaddingStats) { + paddingHunter.outputPaddingInfo(); + } + + return true; +} + +std::optional OIDebugger::generateCode(const irequest &req) { + auto root = OICodeGen::getRootType(*symbols, req); + if (!root.has_value()) { + return std::nullopt; + } + + std::string code = +#include "OITraceCode.cpp" + ; + + auto codegen = OICodeGen::buildFromConfig(generatorConfig); + if (!codegen) { + return nullopt; + } + + RootInfo rootInfo = *root; + codegen->setRootType(rootInfo.type); + if (!codegen->generate(code)) { + LOG(ERROR) << "Failed to generate code for probe: " << req.type << ":" + << req.func << ":" << req.arg; + return std::nullopt; + } + + if (auto sourcePath = cache.getPath(req, OICache::Entity::Source)) { + std::ofstream(*sourcePath) << code; + } + + typeInfos.emplace( + req, + std::make_tuple(RootInfo{rootInfo.varName, codegen->getRootType()}, + codegen->getTypeHierarchy(), codegen->getPaddingInfo())); + + if (!customCodeFile.empty()) { + auto ifs = std::ifstream(customCodeFile); + code.assign(std::istreambuf_iterator(ifs), + std::istreambuf_iterator()); + } + + // Output JIT source-code after codegen has appended the necessary macros + // TODO: Maybe accept file path as an arg to dump the generated code + // instead of stdout + if (VLOG_IS_ON(3)) { + // VLOG truncates output, so use std::cout + VLOG(3) << "Trace code is \n"; + std::cout << code << std::endl; + VLOG(3) << "Trace code dump finished"; + } + + return code; +} diff --git a/src/OIDebugger.h b/src/OIDebugger.h new file mode 100644 index 0000000..0187eb7 --- /dev/null +++ b/src/OIDebugger.h @@ -0,0 +1,278 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include + +#include "OICache.h" +#include "OICodeGen.h" +#include "OICompiler.h" +#include "OIParser.h" +#include "SymbolService.h" +#include "TrapInfo.h" +#include "TreeBuilder.h" +#include "X86InstDefs.h" + +namespace fs = std::filesystem; + +class OIDebugger { + OIDebugger(std::string, OICodeGen::Config, TreeBuilder::Config); + + public: + OIDebugger(pid_t, std::string, OICodeGen::Config, TreeBuilder::Config); + OIDebugger(fs::path, std::string, OICodeGen::Config, TreeBuilder::Config); + bool segmentInit(void); + bool stopTarget(void); + bool interruptTarget(void); + bool compileCode(); + bool processTargetData(); + bool executeCode(pid_t); + void setDataSegmentSize(size_t); + void setGenerateJitDebugInfo(bool genJitDebugInfo) { + compilerConfig.generateJitDebugInfo = genJitDebugInfo; + } + void restoreState(void); + bool segConfigExists(void) const { + return segConfig.existingConfig; + }; + enum oidMode { OID_MODE_THREAD, OID_MODE_FUNC }; + void setMode(oidMode newMode) { + mode = newMode; + }; + bool targetAttach(void); + enum processTrapRet { OID_ERR, OID_CONT, OID_DONE }; + OIDebugger::processTrapRet processTrap(pid_t, bool = true, bool = true); + bool contTargetThread(bool detach = true) const; + bool isGlobalDataProbeEnabled(void) const; + static uint64_t singlestepInst(pid_t, struct user_regs_struct &); + static bool singleStepFunc(pid_t, uint64_t); + bool parseScript(std::istream &script); + bool patchFunctions(); + void stopAll(); + bool removeTraps(pid_t); + bool removeTrap(pid_t, const trapInfo &); + void enableDrgn(); + bool unmapSegments(bool deleteSegConf = false); + bool isInterrupted(void) const { + return oidShouldExit; + }; + + void setCacheBasePath(fs::path basePath) { + if (fs::exists(basePath.parent_path()) && !fs::exists(basePath)) { + // Create cachedir if parent directory exists + // TODO if returning false here, throw an error + fs::create_directory(basePath); + } + cache.basePath = std::move(basePath); + } + + void setCacheRemoteEnabled(bool upload, bool download) { + cache.enableUpload = upload; + cache.enableDownload = download; + cache.abortOnLoadFail = download && !upload; + } + + void setHardDisableDrgn(bool val) { + symbols->setHardDisableDrgn(val); + } + + bool uploadCache() { + return std::all_of( + std::begin(pdata), std::end(pdata), [this](const auto &req) { + return std::all_of( + std::begin(req.args), std::end(req.args), + [this, &req](const auto &arg) { + return cache.upload(irequest{req.type, req.func, arg}); + }); + }); + } + bool downloadCache() { + return std::all_of( + std::begin(pdata), std::end(pdata), [this](const auto &req) { + return std::all_of( + std::begin(req.args), std::end(req.args), + [this, &req](const auto &arg) { + return cache.download(irequest{req.type, req.func, arg}); + }); + }); + }; + + std::pair getTreeBuilderTyping() { + assert(pdata.numReqs() == 1); + auto [type, th, _] = typeInfos.at(pdata.getReq().getReqForArg()); + return {type, th}; + }; + + std::map getPaddingInfo() { + assert(pdata.numReqs() == 1); + return std::get<2>(typeInfos.at(pdata.getReq().getReqForArg())); + } + + void setCustomCodeFile(fs::path newCCT) { + customCodeFile = std::move(newCCT); + } + + void setEnableJitLogging(bool enable) { + enableJitLogging = enable; + } + + private: + std::string configFilePath; + bool debug = false; + bool enableJitLogging = false; + pid_t traceePid{}; + uint64_t objectAddr{}; + oidMode mode{OID_MODE_THREAD}; + enum class SegType { text, data }; + enum class StatusType { + sleep, + traced, + running, + zombie, + dead, + diskSleep, + stopped, + other, + bad + }; + static OIDebugger::StatusType getTaskState(pid_t pid); + static std::string taskStateToString(OIDebugger::StatusType); + size_t dataSegSize{1 << 20}; + size_t textSegSize{(1 << 22) + (1 << 20)}; + std::vector threadList; + ParseData pdata{}; + uint64_t replayInstsCurIdx{}; + bool oidShouldExit{false}; + uint64_t count{}; + bool sigIntHandlerActive{false}; + const int sizeofInt3 = 1; + const int replayInstSize = 512; + bool trapsRemoved{false}; + std::shared_ptr symbols; + OICache cache{}; + + /* + * Map address of valid INT3 instruction to metadata for that interrupt. + * It MUST be an ordered map (std::map) to handle overlapping traps. + */ + std::map> activeTraps; + std::unordered_map> threadTrapState; + std::unordered_map replayInstMap; + + std::unordered_map>> + typeInfos; + + template + std::optional remoteSyscall(Args...); + bool setupLogFile(void); + bool cleanupLogFile(void); + + using ObjectAddrMap = + std::unordered_map, + std::shared_ptr>, + uintptr_t>; + + ObjectAddrMap remoteObjAddrs{}; + + bool setupSegment(SegType); + bool unmapSegment(SegType); + bool writeTargetMemory(void *, void *, size_t) const; + bool readTargetMemory(void *, void *, size_t) const; + std::optional> + locateJitCodeStart(const irequest &, + const OICompiler::RelocResult::SymTable &); + bool writePrologue(const prequest &, + const OICompiler::RelocResult::SymTable &); + bool readInstFromTarget(uintptr_t, uint8_t *, size_t); + void createSegmentConfigFile(void); + void deleteSegmentConfig(bool); + std::optional> makeTrapInfo(const prequest &, + const trapType, + const uint64_t); + bool functionPatch(const prequest &); + bool canProcessTrapForThread(pid_t) const; + bool replayTrappedInstr(const trapInfo &, pid_t, struct user_regs_struct &, + struct user_fpregs_struct &) const; + bool locateObjectsAddresses(const trapInfo &, struct user_regs_struct &); + processTrapRet processFuncTrap(const trapInfo &, pid_t, + struct user_regs_struct &, + struct user_fpregs_struct &); + processTrapRet processJitCodeRet(const trapInfo &, pid_t); + bool processGlobal(const std::string &); + static void dumpRegs(const char *, pid_t, struct user_regs_struct *); + std::optional nextReplayInstrAddr(const trapInfo &); + static int getExtendedWaitEventType(int); + static bool isExtendedWait(int); + void dumpAlltaskStates(void); + std::optional> findRetLocs(FuncDesc &); + + OICompiler::Config compilerConfig{}; + OICodeGen::Config generatorConfig{}; + TreeBuilder::Config treeBuilderConfig{}; + std::optional generateCode(const irequest &); + + std::fstream segmentConfigFile; + fs::path segConfigFilePath; + fs::path customCodeFile; + + struct c { + uintptr_t textSegBase{}; + size_t textSegSize{}; + uintptr_t constStart{}; + uintptr_t jitCodeStart{}; + uintptr_t replayInstBase{}; + bool existingConfig{false}; + uintptr_t dataSegBase{}; + size_t dataSegSize{}; + uintptr_t cookie{}; + int logFile{}; + } segConfig{}; + + /* + * The first 3 words of the data segment contain: + * 1. The OID identifier a.k.a. "magic id", 01DE8 in hex + * 2. A random value (cookie) to make sure that the data + * segment we are reading from was not populated in + * an older run. + * 3. The size of the data segment as written by the JIT-ed + * code. + */ + struct DataHeader { + uintptr_t magicId; + uintptr_t cookie; + uintptr_t size; + + /* + * Flexible Array Member are not standard in C++, but this is + * exactly what we need for the `data` field. These pragmas + * disable the pedantic warnings, so the compiler stops yelling at us. + * We want the header to be the size of the fields above. This is + * important for the `decodeTargetData` method, to give the right size + * to `folly::ByteRange range(…)`. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + uint8_t data[]; +#pragma GCC diagnostic pop + }; + + bool decodeTargetData(const DataHeader &, std::vector &) const; + + static constexpr size_t prologueLength = 64; + static constexpr size_t constLength = 64; +}; diff --git a/src/OIGenerator.cpp b/src/OIGenerator.cpp new file mode 100644 index 0000000..e1ed8fa --- /dev/null +++ b/src/OIGenerator.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OIGenerator.h" + +#include + +#include +#include + +#include "DrgnUtils.h" +#include "OIUtils.h" + +namespace ObjectIntrospection { + +std::vector> +OIGenerator::findOilTypesAndNames(drgnplusplus::program& prog) { + std::vector> out; + + for (auto& func : drgnplusplus::func_iterator(prog)) { + std::string fqdn; + { + char* fqdnChars; + size_t fqdnLen; + if (drgnplusplus::error err( + drgn_type_fully_qualified_name(func.type, &fqdnChars, &fqdnLen)); + err) { + LOG(ERROR) << "error getting drgn type fully qualified name: " << err; + throw err; + } + fqdn = std::string(fqdnChars, fqdnLen); + } + + if (!fqdn.starts_with("ObjectIntrospection::getObjectSize<")) { + continue; + } + if (drgn_type_num_parameters(func.type) != 2) { + continue; + } + if (drgn_type_num_template_parameters(func.type) != 1) { + continue; + } + + auto templateParameters = drgn_type_template_parameters(func.type); + drgn_type_template_parameter param = templateParameters[0]; + + drgn_qualified_type paramType; + if (auto err = drgnplusplus::error( + drgn_template_parameter_type(¶m, ¶mType))) { + LOG(ERROR) << "error getting drgn template parameter type: " << err; + throw err; + } + + LOG(INFO) << "found OIL type: " << drgn_type_name(paramType.type); + + std::string linkageName; + { + char* linkageNameCstr; + if (auto err = drgnplusplus::error( + drgn_type_linkage_name(func.type, &linkageNameCstr))) { + throw err; + } + linkageName = linkageNameCstr; + } + + LOG(INFO) << "found linkage name: " << linkageName; + out.push_back({paramType, linkageName}); + } + + return out; +} + +bool OIGenerator::generateForType(const OICodeGen::Config& generatorConfig, + const OICompiler::Config& compilerConfig, + const drgn_qualified_type& type, + const std::string& linkageName) { + auto codegen = OICodeGen::buildFromConfig(generatorConfig); + if (!codegen) { + LOG(ERROR) << "failed to initialise codegen"; + return false; + } + + std::string code = +#include "OITraceCode.cpp" + ; + + codegen->setRootType(type); + codegen->setLinkageName(linkageName); + + if (!codegen->generateFunctionsForTypesDrgn(code)) { + LOG(ERROR) << "failed to generate code"; + return false; + } + + std::string sourcePath = sourceFileDumpPath; + if (sourceFileDumpPath.empty()) { + // This is the path Clang acts as if it has compiled from e.g. for debug + // information. It does not need to exist. + sourcePath = "oil_jit.cpp"; + } else { + std::ofstream outputFile(sourcePath); + outputFile << code; + } + + OICompiler compiler{{}, compilerConfig}; + return compiler.compile(code, sourcePath, outputPath); +} + +int OIGenerator::generate(fs::path& primaryObject) { + drgnplusplus::program prog; + + { + std::array objectPaths = {{primaryObject.c_str()}}; + if (auto err = drgnplusplus::error(drgn_program_load_debug_info( + prog.get(), std::data(objectPaths), std::size(objectPaths), false, + false))) { + LOG(ERROR) << "error loading debug info program: " << err; + throw err; + } + } + + std::vector> oilTypes = + findOilTypesAndNames(prog); + + if (size_t count = oilTypes.size(); count > 1) { + LOG(WARNING) << "oilgen can currently only generate for one type per " + "compilation unit and we found " + << count; + } + + OICodeGen::Config generatorConfig{}; + OICompiler::Config compilerConfig{}; + if (!OIUtils::processConfigFile(configFilePath, compilerConfig, + generatorConfig)) { + LOG(ERROR) << "failed to process config file"; + return -1; + } + generatorConfig.useDataSegment = false; + + size_t failures = 0; + for (const auto& [type, linkageName] : oilTypes) { + if (!generateForType(generatorConfig, compilerConfig, type, linkageName)) { + LOG(WARNING) << "failed to generate for symbol `" << linkageName + << "`. this is non-fatal but the call will not work."; + failures++; + } + } + + size_t successes = oilTypes.size() - failures; + LOG(INFO) << "object introspection generation complete. " << successes + << " successes and " << failures << " failures."; + return (failures > 0) ? -1 : 0; +} + +} // namespace ObjectIntrospection diff --git a/src/OIGenerator.h b/src/OIGenerator.h new file mode 100644 index 0000000..13cc322 --- /dev/null +++ b/src/OIGenerator.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include + +#include "DrgnUtils.h" +#include "OICodeGen.h" +#include "OICompiler.h" + +namespace fs = std::filesystem; + +namespace ObjectIntrospection { + +class OIGenerator { + public: + int generate(fs::path& primaryObject); + + void setOutputPath(fs::path _outputPath) { + outputPath = std::move(_outputPath); + } + void setConfigFilePath(fs::path _configFilePath) { + configFilePath = std::move(_configFilePath); + } + void setSourceFileDumpPath(fs::path _sourceFileDumpPath) { + sourceFileDumpPath = std::move(_sourceFileDumpPath); + } + + private: + fs::path outputPath; + fs::path configFilePath; + fs::path sourceFileDumpPath; + + std::vector> + findOilTypesAndNames(drgnplusplus::program& prog); + bool generateForType(const OICodeGen::Config& generatorConfig, + const OICompiler::Config& compilerConfig, + const drgn_qualified_type& type, + const std::string& linkageName); +}; + +} // namespace ObjectIntrospection diff --git a/src/OILexer.h b/src/OILexer.h new file mode 100644 index 0000000..ec9c3a8 --- /dev/null +++ b/src/OILexer.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +/* It looks like the yyFlexLexerOnce method is not the correct way of doing + this but it works for now. Fix it in the future. */ +#if !defined(yyFlexLexerOnce) +#include +#endif + +/* #pragma once +#include */ + +#include "OIParser.tab.hh" +#include "location.hh" + +namespace ObjectIntrospection { + +class OIScanner : public yyFlexLexer { + public: + OIScanner(std::istream *in) : yyFlexLexer(in){}; + + virtual ~OIScanner(){}; + + // get rid of override virtual function warning + using FlexLexer::yylex; + + virtual int yylex(OIParser::semantic_type *const lval, + OIParser::location_type *location); + // YY_DECL defined in OILexer.l + // Method body created by flex in OILexer.yy.cc + + private: + /* yyval ptr */ + OIParser::semantic_type *yylval = nullptr; +}; + +} // namespace ObjectIntrospection diff --git a/src/OILexer.l b/src/OILexer.l new file mode 100644 index 0000000..47ba72f --- /dev/null +++ b/src/OILexer.l @@ -0,0 +1,99 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +%{ +/* C++ string header, for string ops below */ +#include +/* Implementation of yyFlexScanner */ +#include "OILexer.h" +#undef YY_DECL +#define YY_DECL \ + int ObjectIntrospection::OIScanner::yylex(ObjectIntrospection::OIParser::semantic_type * const lval, \ + ObjectIntrospection::OIParser::location_type *loc ) + +/* typedef to make the returns for the tokens shorter */ +using token = ObjectIntrospection::OIParser::token; + +/* update location on matching */ +#define YY_USER_ACTION loc->step(); loc->columns(yyleng); +%} + +%option debug +%option nodefault +%option yyclass="ObjectIntrospection::OIScanner" +%option noyywrap +%option c++ + +%x COMMENT + +%% +%{ /** Code executed at the beginning of yylex **/ + yylval = lval; +%} + +(arg[0-9]|retval|this) { + yylval->emplace>(std::list{yytext}); + return( token::OI_ARG ); + } + /* + * We very much rely on the fact that the probetype rule sits before the + * function matching rule below as they'll both match. In that case lex will + * return the first. + */ +(return|entry|global) { + yylval->emplace(yytext); + return( token::OI_PROBETYPE ); + } + + /* oid uses mangled symbols to specify the function */ +[a-zA-Z_0-9.$]+ { + yylval->emplace(yytext); + return( token::OI_FUNC ); + } + +":" { + yylval->emplace(yytext[0]); + return (token::OI_COLON); + } + +<*>\n { + // Update line number + loc->lines(); + } + +, { + yylval->emplace(yytext[0]); + return(token::OI_COMMA); + } + +"//"[^\n]* /* skip one-line comments */ + +"/*" BEGIN(COMMENT); /* skip multi-lines comments */ +[^*\n]* /* skip comment's content */ +"*"+[^*/\n]* /* skip '*' */ +"*"+"/" BEGIN(INITIAL); + +[ \t]+ /* skip whitespace */ + +<> { + if (YYSTATE == COMMENT) { + throw ObjectIntrospection::OIParser::syntax_error( + *loc, "unterminated /* comment"); + } else { + return( token::OI_EOF ); + } + } +%% diff --git a/src/OILibrary.cpp b/src/OILibrary.cpp new file mode 100644 index 0000000..e6e01e6 --- /dev/null +++ b/src/OILibrary.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OILibraryImpl.h" + +bool debug = false; + +namespace ObjectIntrospection { + +bool operator==(const options& lhs, const options& rhs) { + return lhs.configFilePath == rhs.configFilePath && + lhs.cacheDirPath == rhs.cacheDirPath && + lhs.debugFilePath == rhs.debugFilePath && + lhs.debugLevel == rhs.debugLevel && + lhs.chaseRawPointers == rhs.chaseRawPointers; +} + +bool operator!=(const options& lhs, const options& rhs) { + return !(lhs == rhs); +} + +OILibrary::OILibrary(void* TemplateFunc, options opt) : opts(opt) { + this->pimpl_ = new OILibraryImpl(this, TemplateFunc); +} + +OILibrary::~OILibrary() { + delete pimpl_; +} + +int OILibrary::init() { + if (!pimpl_->processConfigFile()) { + return Response::OIL_BAD_CONFIG_FILE; + } + + if (!pimpl_->mapSegment()) { + return Response::OIL_SEGMENT_INIT_FAIL; + } + + pimpl_->initCompiler(); + return pimpl_->compileCode(); +} + +int OILibrary::getObjectSize(void* ObjectAddr, size_t* size) { + if (fp == nullptr) { + return Response::OIL_UNINITIALISED; + } + + *size = (*fp)(ObjectAddr); + return Response::OIL_SUCCESS; +} +} // namespace ObjectIntrospection diff --git a/src/OILibraryImpl.cpp b/src/OILibraryImpl.cpp new file mode 100644 index 0000000..836f804 --- /dev/null +++ b/src/OILibraryImpl.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "OILibraryImpl.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "OIUtils.h" + +extern "C" { +#include +} + +namespace ObjectIntrospection { + +/** + * We need a way to identify the BLOB/Object file, but we don't have access + * to the function's name or the introspected type's name without + * initialising drgn. + * Exclusively to OIL, we won't use the type's name, but the templated + * function address. We assume the address of the templated function + * changes at every compilation, so we don't re-use object files that + * are for an older version of the binary. + */ +const std::string function_identifier(uintptr_t functionAddress) { + return (boost::format("%x") % (uint32_t)(functionAddress % UINT32_MAX)).str(); +} + +OILibraryImpl::OILibraryImpl(OILibrary *self, void *TemplateFunc) + : _self(self), _TemplateFunc(TemplateFunc) { + if (_self->opts.debugLevel != 0) { + google::LogToStderr(); + google::SetStderrLogging(0); + google::SetVLOGLevel("*", _self->opts.debugLevel); + // Upstream glog defines `GLOG_INFO` as 0 https://fburl.com/ydjajhz0, + // but internally it's defined as 1 https://fburl.com/code/9fwams75 + // + // We don't want to link gflags in OIL, so setting it via the flags rather + // than with gflags::SetCommandLineOption + FLAGS_minloglevel = 0; + } +} + +OILibraryImpl::~OILibraryImpl() { + unmapSegment(); +} + +bool OILibraryImpl::mapSegment() { + void *textSeg = + mmap(NULL, segConfig.textSegSize, PROT_EXEC | PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (textSeg == MAP_FAILED) { + PLOG(ERROR) << "error mapping text segment"; + return false; + } + segConfig.textSegBase = textSeg; + + return true; +} + +bool OILibraryImpl::unmapSegment() { + if (segConfig.textSegBase != nullptr && + munmap(segConfig.textSegBase, segConfig.textSegSize) != 0) { + PLOG(ERROR) << "error unmapping text segment"; + return false; + } + + return true; +} + +void OILibraryImpl::initCompiler() { + symbols = std::make_shared(getpid()); + + _cache.symbols = symbols; + + compilerConfig.generateJitDebugInfo = _self->opts.generateJitDebugInfo; + + generatorConfig.useDataSegment = false; + generatorConfig.chaseRawPointers = _self->opts.chaseRawPointers; + generatorConfig.packStructs = true; + generatorConfig.genPaddingStats = false; + + _cache.basePath = _self->opts.cacheDirPath; + _cache.enableUpload = _self->opts.enableUpload; + _cache.enableDownload = _self->opts.enableDownload; + _cache.abortOnLoadFail = _self->opts.abortOnLoadFail; +} + +bool OILibraryImpl::processConfigFile() { + return OIUtils::processConfigFile(_self->opts.configFilePath, compilerConfig, + generatorConfig); +} + +template +class Cleanup { + T resource; + F cleanupFunc; + + public: + Cleanup(T _resource, F _cleanupFunc) + : resource{_resource}, cleanupFunc{_cleanupFunc} {}; + ~Cleanup() { + cleanupFunc(resource); + } +}; + +void close_file(std::FILE *fp) { + std::fclose(fp); +} + +static inline void logElfError(const char *message) { + const char *elf_error_message = elf_errmsg(0); + if (elf_error_message) + LOG(ERROR) << message << ": " << elf_error_message; + else + LOG(ERROR) << message; +} + +int OILibraryImpl::compileCode() { + OICompiler compiler{symbols, compilerConfig}; + + int objectMemfd = memfd_create("oil_object_code", 0); + if (!objectMemfd) { + PLOG(ERROR) << "failed to create memfd for object code"; + return Response::OIL_COMPILATION_FAILURE; + } + + using unique_file_t = std::unique_ptr; + unique_file_t objectStream(fdopen(objectMemfd, "w+"), &close_file); + if (!objectStream) { + PLOG(ERROR) << "failed to convert memfd to stream"; + // This only needs to be cleaned up in the error case, as the fclose + // on the unique_file_t will clean up the underlying fd if it was + // created successfully. + close(objectMemfd); + return Response::OIL_COMPILATION_FAILURE; + } + auto objectPath = + fs::path((boost::format("/dev/fd/%1%") % objectMemfd).str()); + + if (_self->opts.forceJIT) { + struct drgn_program *prog = symbols->getDrgnProgram(); + if (!prog) { + return Response::OIL_COMPILATION_FAILURE; + } + struct drgn_symbol *sym; + if (auto err = drgn_program_find_symbol_by_address( + prog, (uintptr_t)_TemplateFunc, &sym)) { + LOG(ERROR) << "Error when finding symbol by address " << err->code << " " + << err->message; + drgn_error_destroy(err); + return Response::OIL_COMPILATION_FAILURE; + } + const char *name = drgn_symbol_name(sym); + drgn_symbol_destroy(sym); + + auto rootType = + OICodeGen::getRootType(*symbols.get(), irequest{"entry", name, "arg0"}); + if (!rootType.has_value()) { + LOG(ERROR) << "Failed to get type of probe argument"; + return Response::OIL_COMPILATION_FAILURE; + } + + std::string code = +#include "OITraceCode.cpp" + ; + + auto codegen = OICodeGen::buildFromConfig(generatorConfig); + if (!codegen) { + return OIL_COMPILATION_FAILURE; + } + + codegen->setRootType(rootType->type); + if (!codegen->generate(code)) { + return Response::OIL_COMPILATION_FAILURE; + } + + std::string sourcePath = _self->opts.sourceFileDumpPath; + if (_self->opts.sourceFileDumpPath.empty()) { + // This is the path Clang acts as if it has compiled from e.g. for debug + // information. It does not need to exist. + sourcePath = "oil_jit.cpp"; + } else { + std::ofstream outputFile(sourcePath); + outputFile << code; + } + + if (!compiler.compile(code, sourcePath, objectPath)) { + return Response::OIL_COMPILATION_FAILURE; + } + } else { + auto executable = + open(fs::read_symlink("/proc/self/exe").c_str(), O_RDONLY); + if (executable == -1) { + PLOG(ERROR) << "Failed to open executable file"; + return Response::OIL_COMPILATION_FAILURE; + } + auto __executable_cleanup = Cleanup(executable, close); + elf_version(EV_CURRENT); + auto elf = elf_begin(executable, ELF_C_READ, NULL); + auto __elf_cleanup = Cleanup(elf, elf_end); + GElf_Ehdr ehdr; + if (!gelf_getehdr(elf, &ehdr)) { + logElfError("Failed to get ELF object file header"); + return Response::OIL_COMPILATION_FAILURE; + } + size_t string_table_index; + if (elf_getshdrstrndx(elf, &string_table_index) != 0) { + logElfError("Failed to get index of the section header string table"); + return Response::OIL_COMPILATION_FAILURE; + } + + Elf_Scn *section = NULL; + bool done = false; + const auto identifier = function_identifier((uintptr_t)_TemplateFunc); + const auto section_name = OI_SECTION_PREFIX.data() + identifier; + while ((section = elf_nextscn(elf, section))) { + GElf_Shdr section_header; + GElf_Shdr *header = gelf_getshdr(section, §ion_header); + if (!header) + continue; + const char *name = elf_strptr(elf, string_table_index, header->sh_name); + if (name && strcmp(name, section_name.c_str()) == 0) { + Elf_Data *section_data; + if (!(section_data = elf_getdata(section, NULL))) { + LOG(ERROR) << "Failed to get data from section '" << name + << "': " << elf_errmsg(0); + return Response::OIL_COMPILATION_FAILURE; + } + if (section_data->d_size == 0) { + LOG(ERROR) << "Section '" << name << "' is empty"; + return Response::OIL_COMPILATION_FAILURE; + } + if (fwrite(section_data->d_buf, 1, section_data->d_size, + objectStream.get()) < section_data->d_size) { + PLOG(ERROR) + << "Failed to write object file contents to temporary file"; + return Response::OIL_COMPILATION_FAILURE; + } + done = true; + break; + } + } + if (!done) { + LOG(ERROR) << "Did not find section '" << section_name + << "' in the executable"; + return Response::OIL_COMPILATION_FAILURE; + } + fflush(objectStream.get()); + } + + auto relocRes = compiler.applyRelocs( + reinterpret_cast(segConfig.textSegBase), {objectPath}, {}); + if (!relocRes.has_value()) { + return Response::OIL_RELOCATION_FAILURE; + } + + const auto &[_, segments, jitSymbols] = relocRes.value(); + + // Locate the probe's entry point + _self->fp = nullptr; + for (const auto &[symName, symAddr] : jitSymbols) { + if (symName.starts_with("_Z7getSize")) { + _self->fp = (size_t(*)(void *))symAddr; + break; + } + } + if (!_self->fp) + return Response::OIL_RELOCATION_FAILURE; + + // Copy relocated segments in their final destination + for (const auto &[BaseAddr, RelocAddr, Size] : segments) + memcpy((void *)RelocAddr, (void *)BaseAddr, Size); + + return Response::OIL_SUCCESS; +} + +} // namespace ObjectIntrospection diff --git a/src/OILibraryImpl.h b/src/OILibraryImpl.h new file mode 100644 index 0000000..941531e --- /dev/null +++ b/src/OILibraryImpl.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include "OICache.h" +#include "OICodeGen.h" +#include "OICompiler.h" +#include "ObjectIntrospection.h" +#include "SymbolService.h" + +namespace ObjectIntrospection { + +const std::string function_identifier(uintptr_t); + +class OILibraryImpl { + public: + OILibraryImpl(OILibrary *, void *); + ~OILibraryImpl(); + + bool mapSegment(); + bool unmapSegment(); + void initCompiler(); + int compileCode(); + bool processConfigFile(); + void enableLayoutAnalysis(); + + private: + class OILibrary *_self; + + void *_TemplateFunc; + + OICompiler::Config compilerConfig{}; + OICodeGen::Config generatorConfig{}; + std::shared_ptr symbols{}; + + OICache _cache{}; + + struct c { + void *textSegBase = nullptr; + size_t textSegSize = 1u << 22; + } segConfig; +}; +} // namespace ObjectIntrospection diff --git a/src/OIOpts.h b/src/OIOpts.h new file mode 100644 index 0000000..d3207a0 --- /dev/null +++ b/src/OIOpts.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include + +extern "C" { +#include +} + +struct OIOpt { + char shortName; + const char *longName; + int has_arg; + const char *argName; + const char *usage; +}; + +template +class OIOpts { + public: + template + constexpr explicit OIOpts(Opts &&...options) + : _opts{std::forward(options)...} { + // Create the short opts string + size_t shortOptIndex = 0; + for (const auto &opt : _opts) { + _shortOpts[shortOptIndex++] = opt.shortName; + for (int i = 0; i < opt.has_arg; ++i) + _shortOpts[shortOptIndex++] = ':'; + } + + // Pad the remaining with NULL bytes + while (shortOptIndex < _shortOpts.size()) + _shortOpts[shortOptIndex++] = '\0'; + + // Create the array of long opts + for (size_t i = 0; i < _opts.size(); ++i) { + const auto &opt = _opts[i]; + _longOpts[i] = {opt.longName, opt.has_arg, nullptr, opt.shortName}; + } + + // Add empty record to mark the end of long opts + _longOpts[_opts.size()] = {nullptr, no_argument, nullptr, '\0'}; + } + + constexpr const char *shortOpts() const { + return _shortOpts.data(); + } + constexpr const struct option *longOpts() const { + return _longOpts.data(); + } + + template + friend std::ostream &operator<<(std::ostream &os, const OIOpts &opts); + + private: + std::array _opts; + std::array _shortOpts{}; + std::array _longOpts{}; +}; + +template +std::ostream &operator<<(std::ostream &os, const OIOpts &opts) { + int maxLongName = 0; + for (const auto &opt : opts._opts) { + size_t longNameWidth = strlen(opt.longName); + if (opt.argName) + longNameWidth += 1 + strlen(opt.argName); + maxLongName = std::max(maxLongName, (int)longNameWidth); + } + + for (const auto &opt : opts._opts) { + auto fullName = std::string(opt.longName); + if (opt.argName) { + fullName += ' '; + fullName += opt.argName; + } + + os << " -" << opt.shortName << ",--"; + os << std::setw(maxLongName) << std::left; + os << fullName << " "; + + std::string_view usage = opt.usage; + std::string_view::size_type old_pos = 0, new_pos = 0; + while ((new_pos = usage.find('\n', old_pos)) != std::string::npos) { + os << usage.substr(old_pos, new_pos - old_pos + 1); + os << std::setw(maxLongName + 9) << ' '; + old_pos = new_pos + 1; + } + os << usage.substr(old_pos) << '\n'; + } + + return os; +} + +template +OIOpts(Opts... opts) -> OIOpts; diff --git a/src/OIParser.h b/src/OIParser.h new file mode 100644 index 0000000..4d05627 --- /dev/null +++ b/src/OIParser.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include +#include + +struct irequest { + irequest(std::string t, std::string f, std::string a) noexcept + : type(std::move(t)), func(std::move(f)), arg(std::move(a)) { + } + + const std::string type{}, func{}, arg{}; + + [[nodiscard]] bool isReturnRetVal() const noexcept { + return type == "return" && arg == "retval"; + } + + [[nodiscard]] const std::string toString() const { + return type + ":" + func + ":" + arg; + } +}; + +namespace std { + +template <> +struct hash { + std::size_t operator()(const irequest &req) const noexcept { + auto h = hash(); + return h(req.type) ^ h(req.func) ^ h(req.arg); + } +}; + +template <> +struct equal_to { + bool operator()(const irequest &lhs, const irequest &rhs) const noexcept { + return lhs.type == rhs.type && lhs.func == rhs.func && lhs.arg == rhs.arg; + } +}; + +} // namespace std + +struct prequest { + prequest(std::string t, std::string f, std::vector as) noexcept + : type(std::move(t)), func(std::move(f)), args(std::move(as)) { + } + + const std::string type{}, func{}; + const std::vector args{}; + + [[nodiscard]] irequest getReqForArg(size_t idx = 0) const { + if (type == "global") + return {type, func, ""}; + + assert(idx < args.size()); + return {type, func, args[idx]}; + } +}; + +class ParseData { + private: + using RequestVector = std::vector; + RequestVector reqs{}; + + public: + void addReq(std::string type, std::string func, std::list args) { + // Convert the args std::list into a more efficient std::vector + reqs.emplace_back(std::move(type), std::move(func), + std::vector(std::make_move_iterator(args.begin()), + std::make_move_iterator(args.end()))); + } + + size_t numReqs() const noexcept { + return reqs.size(); + } + + [[nodiscard]] const prequest &getReq(size_t idx = 0) const noexcept { + assert(idx < reqs.size()); + return reqs[idx]; + } + + /* Delegate iterator to the RequestVector */ + using iterator = RequestVector::iterator; + using const_iterator = RequestVector::const_iterator; + + iterator begin() noexcept { + return reqs.begin(); + } + const_iterator begin() const noexcept { + return reqs.begin(); + } + const_iterator cbegin() const noexcept { + return reqs.begin(); + } + + iterator end() noexcept { + return reqs.end(); + } + const_iterator end() const noexcept { + return reqs.end(); + } + const_iterator cend() const noexcept { + return reqs.end(); + } +}; diff --git a/src/OIParser.yy b/src/OIParser.yy new file mode 100644 index 0000000..4423200 --- /dev/null +++ b/src/OIParser.yy @@ -0,0 +1,106 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +/* + * This is the bison grammar responsible for generating the ObjectIntrospection::OIParser class. + * This class gives us a number of things worth calling out here if only to + * remind me later :-): + * + * - A variant interface that replaces the C union interface for the + * parsers semantic values. Enabled by setting 'api.value.type variant' below. + */ +%skeleton "lalr1.cc" +%defines +%define api.namespace {ObjectIntrospection} +%define api.parser.class {OIParser} +%define parse.trace +%define parse.error verbose +%define parse.lac full + +%code requires{ + #include + namespace ObjectIntrospection { + class OIScanner; + } + class ParseData; +} + +/* + * ObjectIntrospection::OI_Parser constructor parameters. The scanner object is produced + * by flex and is derived from the yyFlexLexer class. The parser calls + * its yylex() implementation to generate input tokens. The ParseData + * object is spopulated by the lexer/parser introspection specifications + * specified in the input file. + */ +%parse-param { OIScanner &scanner } +%parse-param { ParseData &pdata } + +%code{ +#include +#include +#include +#include +#include +#include "OIParser.h" +#include "OILexer.h" + +#undef yylex +#define yylex scanner.yylex +} + +%define api.value.type variant +%define parse.assert +%locations + +%token OI_COLON +%token OI_PROBETYPE +%token OI_FUNC +%token >OI_ARG +%token OI_COMMA +%token OI_EOF 0 + +%type > oi_args + +%% + +script: oi_blocks OI_EOF + +oi_blocks: oi_block | oi_blocks oi_block + +oi_args: OI_ARG OI_COMMA oi_args + { + $$ = std::move($3); + $$.push_front(std::move($1.front())); + } + | OI_ARG; + +oi_block: OI_PROBETYPE OI_COLON OI_FUNC OI_COLON oi_args + { + pdata.addReq(std::move($1), std::move($3), std::move($5)); + } + | OI_PROBETYPE OI_COLON OI_FUNC + { + pdata.addReq(std::move($1), std::move($3), {}); + } + ; +%% + + +void +ObjectIntrospection::OIParser::error(const location_type &l, const std::string &err_message) +{ + LOG(ERROR) << "OI Parse Error: " << err_message << " at " << l; +} diff --git a/src/OITraceCode.cpp b/src/OITraceCode.cpp new file mode 100644 index 0000000..98a3fca --- /dev/null +++ b/src/OITraceCode.cpp @@ -0,0 +1,157 @@ +R"( +#define NDEBUG 1 +// Required for compatibility with new glibc headers +#define __malloc__(x, y) __malloc__ +#if !__has_builtin(__builtin_free) + #define __builtin_free(x) free(x) +#endif +#pragma clang diagnostic ignored "-Wunknown-attributes" + +// clang-format off +// The header xmmintrin.h must come first. Otherwise it results in errors +// jemalloc during JIT compilation +#include +#include + +#include +#include + +// clang-format on + +#define C10_USING_CUSTOM_GENERATED_MACROS + +// These globals are set by oid, see end of OIDebugger::compileCode() +extern uintptr_t dataBase; +extern size_t dataSize; +extern uintptr_t cookieValue; +extern int logFile; + +constexpr int oidMagicId = 0x01DE8; + +#include + +namespace { + +class { + private: + // 1 MiB of pointers + std::array data; + + // twang_mix64 hash function, taken from Folly where it is used + // as the default hash function for 64-bit integers + constexpr static uint64_t twang_mix64(uint64_t key) noexcept { + key = (~key) + (key << 21); // key *= (1 << 21) - 1; key -= 1; + key = key ^ (key >> 24); + key = key + (key << 3) + (key << 8); // key *= 1 + (1 << 3) + (1 << 8) + key = key ^ (key >> 14); + key = key + (key << 2) + (key << 4); // key *= 1 + (1 << 2) + (1 << 4) + key = key ^ (key >> 28); + key = key + (key << 31); // key *= 1 + (1 << 31) + return key; + } + + public: + void initialize() noexcept { data.fill(0); } + + // Adds the pointer to the set. + // Returns `true` if the value was newly added, + // or `false` if the value was already present. + bool add(uintptr_t pointer) noexcept { + __builtin_assume(pointer > 0); + uint64_t index = twang_mix64(pointer) % data.size(); + while (true) { + uintptr_t entry = data[index]; + if (entry == 0) { + data[index] = pointer; + return true; + } + if (entry == pointer) { + return false; + } + index = (index + 1) % data.size(); + } + } +} static pointers; + +void __jlogptr(uintptr_t ptr) { + static constexpr char hexdigits[] = "0123456789abcdef"; + static constexpr size_t ptrlen = 2 * sizeof(ptr); + + static char hexstr[ptrlen + 1] = {}; + + size_t i = ptrlen; + while (i--) { + hexstr[i] = hexdigits[ptr & 0xf]; + ptr = ptr >> 4; + } + hexstr[ptrlen] = '\n'; + write(logFile, hexstr, sizeof(hexstr)); +} + +} // namespace + +// Unforunately, this is a hack for AdFilterData. +class PredictorInterface; +class PredictionCompositionNode; + +constexpr size_t kGEMMLOWPCacheLineSize = 64; + +template +struct AllocAligned { + // Allocate a T aligned at an `align` byte address + template + static T* alloc(Args&&... args) { + void* p = nullptr; + +#if defined(__ANDROID__) + p = memalign(kGEMMLOWPCacheLineSize, sizeof(T)); +#elif defined(_MSC_VER) + p = _aligned_malloc(sizeof(T), kGEMMLOWPCacheLineSize); +#else + posix_memalign((void**)&p, kGEMMLOWPCacheLineSize, sizeof(T)); +#endif + + if (p) { + return new (p) T(std::forward(args)...); + } + + return nullptr; + } + + // Free a T previously allocated via AllocAligned::alloc() + static void release(T* p) { + if (p) { + p->~T(); +#if defined(_MSC_VER) + _aligned_free((void*)p); +#else + free((void*)p); +#endif + } + } +}; + +// Deleter object for unique_ptr for an aligned object +template +struct AlignedDeleter { + void operator()(T* p) const { AllocAligned::release(p); } +}; + +// alignas(0) is ignored according to docs so can be default +template +struct alignas(align) DummySizedOperator { + char c[N]; +}; + +// The empty class specialization is, unfortunately, necessary. When this operator +// is passed as a template parameter to something like unordered_map, even though +// an empty class and a class with a single character have size one, there is some +// empty class optimization that changes the static size of the container if an +// empty class is passed. + +// DummySizedOperator<0,0> also collapses to this +template <> +struct DummySizedOperator<0> { +}; + +)" diff --git a/src/OIUtils.cpp b/src/OIUtils.cpp new file mode 100644 index 0000000..5bd0e54 --- /dev/null +++ b/src/OIUtils.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 +#include + +#include +#include +#include +#include + +#include "OICodeGen.h" +#include "OICompiler.h" + +namespace OIUtils { + +using namespace std::literals; + +bool processConfigFileToml(const std::string& configFilePath, + OICompiler::Config& compilerConfig, + OICodeGen::Config& generatorConfig) { + toml::table config; + try { + config = toml::parse_file(configFilePath); + } catch (const toml::parse_error& ex) { + LOG(ERROR) << "processConfigFileToml: " << configFilePath << " : " + << ex.description(); + return false; + } + + if (toml::table* types = config["types"].as_table()) { + if (toml::array* arr = (*types)["containers"].as_array()) { + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + generatorConfig.containerConfigPaths.emplace(std::string(el)); + } + }); + } + } + + if (toml::table* headers = config["headers"].as_table()) { + if (toml::array* arr = (*headers)["user_paths"].as_array()) { + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + compilerConfig.userHeaderPaths.emplace_back(el); + } + }); + } + if (toml::array* arr = (*headers)["system_paths"].as_array()) { + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + compilerConfig.sysHeaderPaths.emplace_back(el); + } + }); + } + } + + if (toml::table* codegen = config["codegen"].as_table()) { + if (toml::array* arr = (*codegen)["default_headers"].as_array()) { + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + generatorConfig.defaultHeaders.emplace(el); + } + }); + } + if (toml::array* arr = (*codegen)["default_namespaces"].as_array()) { + arr->for_each([&](auto&& el) { + if constexpr (toml::is_string) { + generatorConfig.defaultNamespaces.emplace(el); + } + }); + } + if (toml::array* arr = (*codegen)["ignore"].as_array()) { + for (auto&& el : *arr) { + if (toml::table* ignore = el.as_table()) { + auto* type = (*ignore)["type"].as_string(); + if (!type) { + LOG(ERROR) << "Config entry 'ignore' must specify a type"; + return false; + } + + auto* members = (*ignore)["members"].as_array(); + if (!members) { + generatorConfig.membersToStub.emplace_back(type->value_or(""sv), + "*"sv); + } else { + for (auto&& member : *members) { + generatorConfig.membersToStub.emplace_back(type->value_or(""sv), + member.value_or(""sv)); + } + } + } + } + } + } + + return true; +} + +bool processConfigFileIni(const std::string& configFilePath, + OICompiler::Config& compilerConfig, + OICodeGen::Config& generatorConfig) { + boost::property_tree::ptree pt; + + try { + boost::property_tree::ini_parser::read_ini(configFilePath, pt); + } catch (const boost::property_tree::ini_parser_error& ex) { + LOG(ERROR) << "processConfigFileIni: " << configFilePath << " : " + << ex.message(); + return false; + } + + // XXX Obviously we don't require the fields so handle non-existent entries + auto userHeaderPaths = pt.get("headers.userPath", ""); + boost::split(compilerConfig.userHeaderPaths, userHeaderPaths, + boost::is_any_of(":")); + + auto systemHeaderPaths = pt.get("headers.systemPath", ""); + boost::split(compilerConfig.sysHeaderPaths, systemHeaderPaths, + boost::is_any_of(":")); + + std::string configHeaders = pt.get("codegen.defaultHeaders", ""); + boost::algorithm::split(generatorConfig.defaultHeaders, configHeaders, + boost::algorithm::is_any_of(":")); + generatorConfig.defaultHeaders.erase(""); + + std::string configNamespaces = + pt.get("codegen.defaultNamespaces", ""); + boost::algorithm::split(generatorConfig.defaultNamespaces, configNamespaces, + boost::algorithm::is_any_of("+")); + generatorConfig.defaultNamespaces.erase(""); + + return true; +} + +bool processConfigFile(const std::string& configFilePath, + OICompiler::Config& compilerConfig, + OICodeGen::Config& generatorConfig) { + // TODO: remove the option to parse as INI entirely + return processConfigFileToml(configFilePath, compilerConfig, + generatorConfig) || + processConfigFileIni(configFilePath, compilerConfig, generatorConfig); +} + +} // namespace OIUtils diff --git a/src/OIUtils.h b/src/OIUtils.h new file mode 100644 index 0000000..f1783a4 --- /dev/null +++ b/src/OIUtils.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include "OICodeGen.h" +#include "OICompiler.h" + +namespace OIUtils { +bool processConfigFile(const std::string& configFilePath, + OICompiler::Config& compilerConfig, + OICodeGen::Config& generatorConfig); +} diff --git a/src/PaddingHunter.cpp b/src/PaddingHunter.cpp new file mode 100644 index 0000000..2dbe440 --- /dev/null +++ b/src/PaddingHunter.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "PaddingHunter.h" + +#include +#include + +void PaddingHunter::processLocalPaddingInfo() { + for (auto &lPS : localPaddedStructs) { + if (paddedStructs.find(lPS.first) != paddedStructs.end()) { + if (localPaddedStructs[lPS.first].instancesCnt > + paddedStructs[lPS.first].instancesCnt) { + paddedStructs[lPS.first].instancesCnt = + localPaddedStructs[lPS.first].instancesCnt; + } + } else { + paddedStructs[lPS.first] = lPS.second; + } + } +} + +void PaddingHunter::outputPaddingInfo() { + std::ofstream paddingStatsFile; + paddingStatsFile.open(paddingStatsFileName); + uint64_t sum = 0; + + std::vector> paddedStructsVec; + for (auto &paddedStruct : paddedStructs) { + paddedStructsVec.push_back({paddedStruct.first, paddedStruct.second}); + } + + for (auto &paddedStruct : paddedStructsVec) { + sum += paddedStruct.second.paddingSize * paddedStruct.second.instancesCnt; + } + + paddingStatsFile << "Total Saving Opportunity: " << sum << "\n\n\n"; + + std::sort(paddedStructsVec.begin(), paddedStructsVec.end(), + [](const std::pair &left, + const std::pair &right) { + return left.second.instancesCnt * left.second.savingSize > + right.second.instancesCnt * right.second.savingSize; + }); + + for (auto &paddedStruct : paddedStructsVec) { + paddingStatsFile << "Name: " << paddedStruct.first + << ", object size: " << paddedStruct.second.structSize + << ", saving size: " << paddedStruct.second.savingSize + << ", padding size: " << paddedStruct.second.paddingSize + << ", isSet size: " << paddedStruct.second.isSetSize + << ", instance_cnt: " << paddedStruct.second.instancesCnt + << "\nSaving opportunity: " + << paddedStruct.second.savingSize * + paddedStruct.second.instancesCnt + << " bytes\n\n" + << paddedStruct.second.definition << "\n\n\n"; + } + paddingStatsFile.close(); +} diff --git a/src/PaddingHunter.h b/src/PaddingHunter.h new file mode 100644 index 0000000..3c5baec --- /dev/null +++ b/src/PaddingHunter.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include + +struct PaddingInfo { + public: + PaddingInfo() = default; + PaddingInfo(size_t strSize, int saveSz, size_t paddSz, size_t issetSz, + std::string def, size_t instCnt) + : structSize{strSize}, + alignmentRequirement{8}, + savingSize{static_cast(saveSz)}, + paddingSize{paddSz}, + isSetSize{issetSz}, + isSetOffset{0}, + definition{def}, + instancesCnt{instCnt}, + isThriftStruct{false} {}; + + size_t structSize; + size_t alignmentRequirement; + size_t savingSize; + size_t paddingSize; + size_t isSetSize; + size_t isSetOffset; + std::string definition; + size_t instancesCnt; + bool isThriftStruct; + std::vector paddings; + + size_t savingFromPacking() const { + size_t unpackedSize = isSetSize; + size_t packedSize = (unpackedSize + 8 - 1) / 8; + + return unpackedSize - packedSize; + } + + void computeSaving() { + /* Sum of members whose size is not multiple of alignment */ + size_t oddSum = 0; + savingSize = 0; + + for (size_t padding : paddings) { + oddSum += (alignmentRequirement - padding / 8); + } + + if (isThriftStruct) { + if (isSetSize) { + savingSize = savingFromPacking(); + oddSum += isSetOffset - savingFromPacking(); + } + + savingSize += + paddingSize - (alignmentRequirement - oddSum % alignmentRequirement) % + alignmentRequirement; + } else { + savingSize = paddingSize; + } + } +}; + +class PaddingHunter { + public: + std::map paddedStructs; + std::map localPaddedStructs; + std::string paddingStatsFileName = "PADDING"; + + // we do a max reduction on instance count across the probe points + void processLocalPaddingInfo(); + + void outputPaddingInfo(); +}; diff --git a/src/Serialize.cpp b/src/Serialize.cpp new file mode 100644 index 0000000..77909d2 --- /dev/null +++ b/src/Serialize.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "Serialize.h" + +#include +#include +#include +#include + +#include "OICodeGen.h" + +namespace boost::serialization { + +template +void verify_version(const unsigned int version) { + const auto expected_version = boost::serialization::version::value; + if (expected_version != version) { + auto error = (boost::format("Failed to serialize type `%1%`, as the class " + "version did not match " + "(cache had version %2%" + ", but OID expected version %3%)") % + typeid(T).name() % version % expected_version) + .str(); + throw std::runtime_error(error); + } +} + +using iarchive = boost::archive::text_iarchive; +using oarchive = boost::archive::text_oarchive; + +// The default value for `boost::serialization::version` for a class is 0 +// if it is not specified via `BOOST_CLASS_VERSION`. Therefore the +// `static_assert` in the below macro prevents us from accidentally +// serializing a new class without explicitly setting a version for it. +#define INSTANCIATE_SERIALIZE(Type) \ + static_assert( \ + boost::serialization::version::value > 0, \ + "No class version was defined for type `" #Type \ + "`, please add an invocation of `DEFINE_TYPE_VERSION` for this " \ + "type."); \ + template void serialize(iarchive &, Type &, const unsigned int); \ + template void serialize(oarchive &, Type &, const unsigned int); + +template +void serialize(Archive &ar, PaddingInfo &p, const unsigned int version) { + verify_version(version); + ar &p.structSize; + ar &p.paddingSize; + ar &p.definition; + ar &p.instancesCnt; + ar &p.savingSize; +} + +INSTANCIATE_SERIALIZE(PaddingInfo) + +template +void serialize(Archive &ar, ContainerInfo &info, const unsigned int version) { + verify_version(version); + ar &info.typeName; + // Unfortunately boost serialization doesn't support `std::optional`, + // so we have to do this ourselves + size_t numTemplateParams = 0; + if (Archive::is_saving::value) { + numTemplateParams = + info.numTemplateParams.value_or(std::numeric_limits::max()); + } + ar &numTemplateParams; + if (Archive::is_loading::value) { + if (numTemplateParams == std::numeric_limits::max()) { + info.numTemplateParams = std::nullopt; + } else { + info.numTemplateParams = numTemplateParams; + } + } + ar &info.ctype; + ar &info.header; + ar &info.ns; +} + +INSTANCIATE_SERIALIZE(ContainerInfo) + +template +void serialize(Archive &ar, struct drgn_location_description &location, + const unsigned int version) { + verify_version(version); + ar &location.start; + ar &location.end; + ar &location.expr_size; + if (Archive::is_loading::value) { + // It is important to call `malloc` here instead of allocating with `new` + // since these structs are usually allocated and deallocated directly by + // `drgn`, which is written in C. + location.expr = + (const char *)malloc(sizeof(*location.expr) * location.expr_size); + } + ar &make_array(const_cast(location.expr), location.expr_size); +} + +INSTANCIATE_SERIALIZE(struct drgn_location_description) + +template +void serialize(Archive &ar, struct drgn_object_locator &locator, + const unsigned int version) { + verify_version(version); + ar &locator.module_start; + ar &locator.module_end; + ar &locator.module_bias; + ar &locator.locations_size; + ar &locator.frame_base_locations_size; + if (Archive::is_loading::value) { + // It is important to call `malloc` here instead of allocating with `new` + // since these structs are usually allocated and deallocated directly by + // `drgn`, which is written in C. + locator.locations = (struct drgn_location_description *)malloc( + sizeof(*locator.locations) * locator.locations_size); + locator.frame_base_locations = (struct drgn_location_description *)malloc( + sizeof(*locator.frame_base_locations) * + locator.frame_base_locations_size); + } + ar &make_array(locator.locations, + locator.locations_size); + ar &make_array( + locator.frame_base_locations, locator.frame_base_locations_size); + ar &locator.qualified_type; +} + +INSTANCIATE_SERIALIZE(struct drgn_object_locator) + +template +void serialize(Archive &ar, FuncDesc::Arg &arg, const unsigned int version) { + verify_version(version); + ar &arg.typeName; + ar &arg.valid; + ar &arg.locator; +} + +INSTANCIATE_SERIALIZE(FuncDesc::Arg) + +template +void serialize(Archive &ar, FuncDesc::Retval &retval, + const unsigned int version) { + verify_version(version); + ar &retval.typeName; + ar &retval.valid; +} + +INSTANCIATE_SERIALIZE(FuncDesc::Retval) + +template +void serialize(Archive &ar, FuncDesc::Range &range, + const unsigned int version) { + verify_version(version); + ar &range.start; + ar &range.end; +} + +INSTANCIATE_SERIALIZE(FuncDesc::Range) + +template +void serialize(Archive &ar, FuncDesc &fd, const unsigned int version) { + verify_version(version); + ar &fd.symName; + ar &fd.ranges; + ar &fd.isMethod; + ar &fd.arguments; + ar &fd.retval; +} + +INSTANCIATE_SERIALIZE(FuncDesc) + +template +void serialize(Archive &ar, GlobalDesc &gd, const unsigned int version) { + verify_version(version); + ar &gd.symName; + ar &gd.typeName; + ar &gd.baseAddr; +} + +INSTANCIATE_SERIALIZE(GlobalDesc) + +template +static void serialize_c_string(Archive &ar, char **string) { + size_t length; + if (Archive::is_saving::value) { + length = *string ? strlen(*string) : 0; + } + ar &length; + if (Archive::is_loading::value) { + *string = length ? (char *)malloc(sizeof(char) * (length + 1)) : NULL; + } + if (length > 0) { + ar &make_array(*string, length + 1); + } +} + +// ################################# CAUTION ################################# +// The below code is *very* defensive and *very* precisely structured. Please +// DO NOT modify it without careful consideration and deliberation, as you +// are liable to break things otherwise. Something as simple as changing the +// order of two lines can cause cache corruption, so please make sure you know +// what you're doing (or ask someone who does) before touching anything. +// ########################################################################### +template +void serialize(Archive &ar, struct drgn_type &type, + const unsigned int version) { +#define assert_in_same_union(member_1, member_2) \ + do { \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wpedantic\""); \ + static_assert(offsetof(typeof(type._private), member_1) == \ + offsetof(typeof(type._private), member_2)); \ + _Pragma("GCC diagnostic pop"); \ + } while (0) + + verify_version(version); + // We want to ensure that if the definition of `struct drgn_type` is changed + // at any point in the future that our code stops compiling, instead of us + // silently ignoring any newly added fields. + static_assert(sizeof(type) == 120); + + // Ensure any unused fields are zeroed out for safety, as to avoid subtle + // bugs resulting from mistakenly unserialized fields containing garbage. + if (Archive::is_loading::value) { + memset(&type, 0, sizeof(type)); + } + + // For the most part, we serialize fields in the order they are declared to + // make it easy to visually confirm that we haven't missed anything. + // + // `.kind` MUST be serialized first, not just because it's declared first, + // but because all of the `drgn_type_has_*` functions rely on the value of + // `.kind` + ar &type._private.kind; + ar &type._private.is_complete; + ar &type._private.primitive; + ar &type._private.qualifiers; + // `.program` is NULL, per the initial `memset` + + if (Archive::is_loading::value) { + type._private.language = &drgn_language_cpp; + } + + // AVOIDING OVERSERIALIZATION: + // Many drgn structures contain pointers to `struct drgn_type`. We avoid + // serializing these structures (and therefore avoid recursively serializing + // `struct drgn_type`s in order to avoid serializing massive amounts of data. + // In other words, our serialization of `struct drgn_type` is shallow. + + // First union: `name`, `tag`, `num_parameters` + assert_in_same_union(name, tag); + assert_in_same_union(name, num_parameters); + if (drgn_type_has_name(&type)) { + serialize_c_string(ar, const_cast(&type._private.name)); + } else if (drgn_type_has_tag(&type)) { + serialize_c_string(ar, const_cast(&type._private.tag)); + } else if (drgn_type_has_parameters(&type)) { + // Leave `num_parameters` set to 0 per the initial `memset`, + // see "AVOIDING OVERSERIALIZATION" comment above + } + + // Second union: `size`, `length`, `num_enumerators`, `is_variadic` + assert_in_same_union(size, length); + assert_in_same_union(size, num_enumerators); + assert_in_same_union(size, is_variadic); + if (drgn_type_has_size(&type)) { + ar &type._private.size; + } else if (drgn_type_has_length(&type)) { + ar &type._private.length; + } else if (drgn_type_has_enumerators(&type)) { + ar &type._private.num_enumerators; + } else if (drgn_type_has_is_variadic(&type)) { + ar &type._private.is_variadic; + } + + // Third union: `little_endian`, `members`, `enumerators`, `parameters` + assert_in_same_union(little_endian, members); + assert_in_same_union(little_endian, enumerators); + assert_in_same_union(little_endian, parameters); + if (drgn_type_has_little_endian(&type)) { + ar &type._private.little_endian; + } else if (drgn_type_has_members(&type)) { + // Leave `members` set to NULL per the initial `memset`, + // see "AVOIDING OVERSERIALIZATION" comment above + } else if (drgn_type_has_enumerators(&type)) { + // Leave `enumerators` set to NULL per the initial `memset`, + // see "AVOIDING OVERSERIALIZATION" comment above + } else if (drgn_type_has_parameters(&type)) { + // Leave `parameters` set to NULL per the initial `memset`, + // see "AVOIDING OVERSERIALIZATION" comment above + } + + // Leave `template_parameters`, `parents`, `num_template_parameters`, + // and `num_parents` set to NULL/0 per the initial `memset`, see + // "AVOIDING OVERSERIALIZATION" comment above + + ar &type._private.die_addr; + // `.module` is NULL, per the initial `memset` + if (Archive::is_saving::value) { + struct drgn_error *err = drgn_type_sizeof(&type, &type._private.oi_size); + if (err) { + drgn_error_destroy(err); + type._private.oi_size = + std::numeric_limits::max(); + } + } + ar &type._private.oi_size; + + // It's important that `oi_name` is declared here and not inside the + // if statement so that its data isn't freed when we call + // `serialize_c_string`. + std::string oi_name; + if (Archive::is_saving::value) { + oi_name = OICodeGen::typeToName(&type); + type._private.oi_name = oi_name.c_str(); + } + serialize_c_string(ar, const_cast(&type._private.oi_name)); + + if (drgn_type_kind(&type) == DRGN_TYPE_ARRAY) { + ar &type._private.type; + } +#undef assert_in_same_union +} + +INSTANCIATE_SERIALIZE(struct drgn_type) + +template +void serialize(Archive &ar, struct DrgnClassMemberInfo &m, + const unsigned int version) { + verify_version(version); + ar &m.type; + ar &m.member_name; + ar &m.bit_offset; + ar &m.bit_field_size; +} + +INSTANCIATE_SERIALIZE(DrgnClassMemberInfo) + +template +void serialize(Archive &ar, struct drgn_qualified_type &type, + const unsigned int version) { + verify_version(version); + ar &type.type; + ar &type.qualifiers; +} + +INSTANCIATE_SERIALIZE(struct drgn_qualified_type) + +template +void serialize(Archive &ar, RootInfo &rootInfo, const unsigned int version) { + verify_version(version); + ar &rootInfo.varName; + ar &rootInfo.type; +} + +INSTANCIATE_SERIALIZE(RootInfo) + +template +void serialize(Archive &ar, struct TypeHierarchy &th, + const unsigned int version) { + verify_version(version); + ar &th.classMembersMap; + ar &th.containerTypeMap; + ar &th.typedefMap; + ar &th.sizeMap; + ar &th.knownDummyTypeList; + ar &th.pointerToTypeMap; + ar &th.thriftIssetStructTypes; +} + +INSTANCIATE_SERIALIZE(struct TypeHierarchy) +// INSTANCIATE_SERIALIZE(std::map) + +} // namespace boost::serialization diff --git a/src/Serialize.h b/src/Serialize.h new file mode 100644 index 0000000..bae2c1b --- /dev/null +++ b/src/Serialize.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common.h" +#include "ContainerInfo.h" +#include "PaddingHunter.h" +#include "SymbolService.h" + +#define DEFINE_TYPE_VERSION(Type, size, version) \ + static_assert( \ + sizeof(Type) == size, \ + "Type `" #Type \ + "` has changed, please update the `size` parameter and increment the " \ + "`version` parameter of the corresponding invocation " \ + "of `DEFINE_TYPE_VERSION` in " __FILE__); \ + BOOST_CLASS_VERSION(Type, version) + +DEFINE_TYPE_VERSION(PaddingInfo, 120, 3) +DEFINE_TYPE_VERSION(ContainerInfo, 168, 4) +DEFINE_TYPE_VERSION(struct drgn_location_description, 32, 2) +DEFINE_TYPE_VERSION(struct drgn_object_locator, 72, 2) +DEFINE_TYPE_VERSION(FuncDesc::Arg, 128, 2) +DEFINE_TYPE_VERSION(FuncDesc::Retval, 56, 2) +DEFINE_TYPE_VERSION(FuncDesc::Range, 16, 2) +DEFINE_TYPE_VERSION(FuncDesc, 104, 4) +DEFINE_TYPE_VERSION(GlobalDesc, 72, 4) +DEFINE_TYPE_VERSION(struct drgn_type, 120, 3) +DEFINE_TYPE_VERSION(DrgnClassMemberInfo, 64, 3) +DEFINE_TYPE_VERSION(struct drgn_qualified_type, 16, 2) +DEFINE_TYPE_VERSION(RootInfo, 48, 2) +DEFINE_TYPE_VERSION(TypeHierarchy, 336, 5) + +#undef DEFINE_TYPE_VERSION + +namespace boost::serialization { + +#define DECL_SERIALIZE(Type) \ + template \ + void serialize(Archive &, Type &, const unsigned int) + +DECL_SERIALIZE(PaddingInfo); +DECL_SERIALIZE(ContainerInfo); + +DECL_SERIALIZE(FuncDesc::Arg); +DECL_SERIALIZE(FuncDesc); +DECL_SERIALIZE(GlobalDesc); + +DECL_SERIALIZE(struct drgn_type); +DECL_SERIALIZE(struct drgn_qualified_type); +DECL_SERIALIZE(RootInfo); + +DECL_SERIALIZE(DrgnClassMemberInfo); +DECL_SERIALIZE(TypeHierarchy); + +#undef DECL_SERIALIZE + +} // namespace boost::serialization diff --git a/src/SymbolService.cpp b/src/SymbolService.cpp new file mode 100644 index 0000000..d1b75b1 --- /dev/null +++ b/src/SymbolService.cpp @@ -0,0 +1,731 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "SymbolService.h" + +#include + +#include +#include +#include +#include +#include + +#include "OICodeGen.h" +#include "OIParser.h" + +extern "C" { +#include +#include + +#include "drgn.h" +#include "dwarf.h" +} + +static bool LoadExecutableAddressRange( + pid_t pid, std::vector> &exeAddrs) { + std::ifstream f("/proc/" + std::to_string(pid) + "/maps"); + + if (f.is_open()) { + std::string line; + uint64_t start = 0; + uint64_t end = 0; + uint64_t offset = 0; + uint64_t inode = 0; + uint dmajor = 0; + uint dminor = 0; + int nread = -1; + constexpr int permissionsLen = 4; + char perm[permissionsLen + 1]; + + while (std::getline(f, line)) { + if (sscanf(line.c_str(), + "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %" PRIu64 " %n", + &start, &end, perm, &offset, &dmajor, &dminor, &inode, + &nread) < 7 || + nread <= 0) { + return false; + } + + if (strlen(perm) != permissionsLen) { + return false; + } + + if (perm[2] == 'x') { + exeAddrs.emplace_back(start, end); + } + } + } + return true; +} + +#undef PREMISSIONS_LEN + +static bool isExecutableAddr( + uint64_t addr, const std::vector> &exeAddrs) { + assert(std::is_sorted(begin(exeAddrs), end(exeAddrs))); + + // Find the smallest exeAddrs range where addr < range.end + auto it = std::upper_bound( + begin(exeAddrs), end(exeAddrs), std::make_pair(addr, addr), + [](const auto &r1, const auto &r2) { return r1.second < r2.second; }); + + return it != end(exeAddrs) && addr >= it->first; +} + +SymbolService::SymbolService(std::variant newTarget) { + target = std::move(newTarget); + + if (target.index() == 0) { + // Update target processes memory map + LoadExecutableAddressRange(std::get(target), executableAddrs); + } +} + +SymbolService::~SymbolService() { + if (prog != nullptr) { + drgn_program_destroy(prog); + } +} + +struct ModParams { + const char *st; + GElf_Sym s; + GElf_Addr value; + std::vector> &exeAddrs; +}; + +/** + * Callback for dwfl_getmodules(). For the provided module we iterate + * through its symbol table and look for the given symbol. Values + * are passed in and out via the 'arg' parameter. + * + * @param[in] arg[0] - The symbol to locate. + * @param[out] arg[1] - Symbol information if found. + * @param[out] arg[2] - Address of the symbol if found. + * + */ + +static int moduleCallback(Dwfl_Module *mod, void ** /* userData */, + const char *name, Dwarf_Addr /* start */, void *arg) { + ModParams *m = (ModParams *)arg; + + int nsym = dwfl_module_getsymtab(mod); + VLOG(1) << "mod name: " << name << " " + << "nsyms " << nsym; + + // FIXME: There's surely a better way to distinguish debuginfo modules from + // actual code modules. + char debugSuffix[] = ".debuginfo"; + size_t debugSuffixLen = sizeof(debugSuffix) - 1; + size_t nameLen = strlen(name); + + if (debugSuffixLen <= nameLen) { + if (strncmp(name + nameLen - debugSuffixLen, debugSuffix, debugSuffixLen) == + 0) { + VLOG(1) << "Skipping debuginfo module"; + m->value = 0; + return DWARF_CB_OK; + } + } + + /* I think the first entry is always UNDEF */ + for (int i = 1; i < nsym; ++i) { + Elf *elf = nullptr; + GElf_Word shndxp = 0; + + const char *sname = dwfl_module_getsym_info(mod, i, &m->s, &m->value, + &shndxp, &elf, nullptr); + + if (sname == nullptr || sname[0] == '\0') { + continue; + } + + switch + GELF_ST_TYPE(m->s.st_info) { + case STT_SECTION: + case STT_FILE: + case STT_TLS: + case STT_NOTYPE: + break; + + case STT_OBJECT: + if (shndxp != SHN_UNDEF && m->st && !strcmp(sname, m->st)) { + VLOG(1) << "Symbol lookup successful for " << sname << " in module " + << name; + m->st = nullptr; + return DWARF_CB_ABORT; + } + break; + + default: + /* + * I don't understand why the only symbol that is presented + * to us here has NOTYPE yet readelf shows me it is defined + * as an STT_FUNC. Confused... + */ + if (shndxp != SHN_UNDEF && m->st && !strcmp(sname, m->st) && + isExecutableAddr(m->value, m->exeAddrs)) { + m->st = nullptr; + + VLOG(1) << "Symbol lookup successful for " << sname << " in module " + << name; + + return DWARF_CB_ABORT; + } + break; + } + } + + // Set m->value to 0 if symbol is not found + m->value = 0; + return DWARF_CB_OK; +} + +/** + * Resolve a symbol to its location in the target ELF binary. + * + * @param[in] symName - symbol to resolve + * @return - A std::optional with the symbol's information + */ +std::optional SymbolService::locateSymbol( + const std::string &symName) { + static char *debuginfo_path; + static const Dwfl_Callbacks proc_callbacks{ + .find_elf = dwfl_linux_proc_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + .section_address = dwfl_offline_section_address, + .debuginfo_path = &debuginfo_path, + }; + + Dwfl *dwfl = dwfl_begin(&proc_callbacks); + if (dwfl == nullptr) { + LOG(ERROR) << "dwfl_begin: " << dwfl_errmsg(dwfl_errno()); + return std::nullopt; + } + BOOST_SCOPE_EXIT_ALL(&) { + dwfl_end(dwfl); + }; + + switch (target.index()) { + case 0: { + auto pid = std::get(target); + if (int err = dwfl_linux_proc_report(dwfl, pid)) { + LOG(ERROR) << "dwfl_linux_proc_report: " << dwfl_errmsg(err); + return std::nullopt; + } + break; + } + case 1: { + const auto &exe = std::get(target); + Dwfl_Module *mod = + dwfl_report_offline(dwfl, exe.c_str(), exe.c_str(), -1); + if (mod == nullptr) { + LOG(ERROR) << "dwfl_report_offline: " << dwfl_errmsg(dwfl_errno()); + return std::nullopt; + } + + Dwarf_Addr start = 0; + Dwarf_Addr end = 0; + if (dwfl_module_info(mod, nullptr, &start, &end, nullptr, nullptr, + nullptr, nullptr) == nullptr) { + LOG(ERROR) << "dwfl_module_info: " << dwfl_errmsg(dwfl_errno()); + return std::nullopt; + } + + VLOG(1) << "Module info for " << exe << ": start= " << std::hex << start + << ", end=" << end; + + // Add module's boundary to executableAddrs + executableAddrs = {{start, end}}; + + break; + } + } + + if (dwfl_report_end(dwfl, nullptr, nullptr) != 0) { + LOG(ERROR) << "dwfl_report_end: " << dwfl_errmsg(-1); + } + + ModParams m = { + .st = symName.c_str(), .s = {}, .value = 0, .exeAddrs = executableAddrs}; + + dwfl_getmodules(dwfl, moduleCallback, (void *)&m, 0); + + if (m.value == 0) { + return std::nullopt; + } + return SymbolInfo{m.value, m.s.st_size}; +} + +static std::string bytesToHexString(const unsigned char *bytes, int nbbytes) { + static const char characters[] = "0123456789abcdef"; + + std::string ret(nbbytes * 2, 0); + + for (int i = 0; i < nbbytes; ++i) { + ret[2 * i] = characters[bytes[i] >> 4]; + ret[2 * i + 1] = characters[bytes[i] & 0x0F]; + } + + return ret; +} + +/** + * Callback for dwfl_getmodules(). For the provided module we lookup + * its build ID and pass it back via the 'arg' parameter. + * We expect the target program to always be the first module passed + * to this callback. So we always return DWARF_CB_ABORT, as this is + * the only build ID we are interested in. + */ +static int buildIDCallback(Dwfl_Module *mod, void ** /* userData */, + const char *name, Dwarf_Addr /* start */, + void *arg) { + auto *buildID = static_cast *>(arg); + + // We must call dwfl_module_getelf before using dwfl_module_build_id + GElf_Addr bias = 0; + Elf *elf = dwfl_module_getelf(mod, &bias); + if (elf == nullptr) { + LOG(ERROR) << "Failed to getelf for " << name << ": " << dwfl_errmsg(-1); + return DWARF_CB_ABORT; + } + + GElf_Addr vaddr = 0; + const unsigned char *bytes = nullptr; + + int nbbytes = dwfl_module_build_id(mod, &bytes, &vaddr); + if (nbbytes <= 0) { + *buildID = std::nullopt; + LOG(ERROR) << "Build ID not found for " << name; + } else { + *buildID = bytesToHexString(bytes, nbbytes); + VLOG(1) << "Build ID lookup successful for " << name << ": " + << buildID->value(); + } + + return DWARF_CB_ABORT; +} + +std::optional SymbolService::locateBuildID() { + static char *debuginfoPath; + static const Dwfl_Callbacks procCallbacks = { + .find_elf = dwfl_linux_proc_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + .section_address = dwfl_offline_section_address, + .debuginfo_path = &debuginfoPath, + }; + + Dwfl *dwfl = dwfl_begin(&procCallbacks); + if (dwfl == nullptr) { + LOG(ERROR) << "dwfl_begin: " << dwfl_errmsg(dwfl_errno()); + return std::nullopt; + } + + BOOST_SCOPE_EXIT_ALL(&) { + dwfl_end(dwfl); + }; + + switch (target.index()) { + case 0: { + auto pid = std::get(target); + if (auto err = dwfl_linux_proc_report(dwfl, pid)) { + LOG(ERROR) << "dwfl_linux_proc_report: " << dwfl_errmsg(err); + } + break; + } + case 1: { + const auto &exe = std::get(target); + if (dwfl_report_offline(dwfl, exe.c_str(), exe.c_str(), -1) == nullptr) { + LOG(ERROR) << "dwfl_report_offline: " << dwfl_errmsg(dwfl_errno()); + return std::nullopt; + } + break; + } + } + + if (dwfl_report_end(dwfl, nullptr, nullptr) != 0) { + LOG(ERROR) << "dwfl_report_end: " << dwfl_errmsg(-1); + } + + std::optional buildID; + dwfl_getmodules(dwfl, buildIDCallback, (void *)&buildID, 0); + + return buildID; +} + +struct drgn_program *SymbolService::getDrgnProgram() { + if (hardDisableDrgn) { + LOG(ERROR) << "drgn is disabled, refusing to initialize"; + return nullptr; + } + + if (prog != nullptr) { + return prog; + } + + LOG(INFO) << "Initialising drgn. This might take a while"; + switch (target.index()) { + case 0: { + if (auto *err = drgn_program_from_pid(std::get(target), &prog)) { + LOG(ERROR) << "Failed to initialize drgn: " << err->code << " " + << err->message; + return nullptr; + } + auto executable = fs::read_symlink( + "/proc/" + std::to_string(std::get(target)) + "/exe"); + const auto *executableCStr = executable.c_str(); + if (auto *err = drgn_program_load_debug_info(prog, &executableCStr, 1, + false, false)) { + LOG(ERROR) << "Error loading debug info: " << err->message; + return nullptr; + } + break; + } + case 1: { + if (auto *err = drgn_program_create(nullptr, &prog)) { + LOG(ERROR) << "Failed to create empty drgn program: " << err->code + << " " << err->message; + return nullptr; + } + + const char *path = std::get(target).c_str(); + if (auto *err = + drgn_program_load_debug_info(prog, &path, 1, false, false)) { + LOG(ERROR) << "Failed to read debug info: " << err->code << " " + << err->message; + drgn_program_destroy(prog); + + prog = nullptr; + return prog; + } + + LOG(INFO) << "Successfully read debug info"; + break; + } + } + + return prog; +} + +/* + * Although 'parseFormalParam' has an all-encompassing sounding name, its sole + * task is to extract the location information for this parameter if any exist. + */ +static void parseFormalParam(Dwarf_Die ¶m, struct drgn_module *module, + struct drgn_program *prog, Dwarf_Die &funcDie, + std::shared_ptr &fd) { + /* + * NOTE: It is vital that the function descriptors list of arguments + * are in order and that an entry exists for each argument position + * even if something goes wrong here when extracting the formal parameter. + * We *must* pay careful attention to that especially when introducing + * any new error handling. + */ + auto farg = fd->addArgument(); + auto *err = + drgn_object_locator_init(prog, module, &funcDie, ¶m, &farg->locator); + if (err) { + LOG(ERROR) << "Could not initialize drgn_object_locator for parameter: " + << err->code << ", " << err->message; + farg->valid = false; + return; + } + + const char *name = nullptr; + Dwarf_Attribute attr; + if (dwarf_attr_integrate(¶m, DW_AT_name, &attr)) { + if (!(name = dwarf_formstring(&attr))) { + LOG(ERROR) << "DW_AT_name exists but no name extracted"; + } + } else { + VLOG(1) << "Parameter has no DW_AT_name attribute!"; + } + + if (name && !strcmp(name, "this")) { + VLOG(1) << "'this' pointer found"; + fd->isMethod = true; + } + + farg->typeName = + SymbolService::getTypeName(farg->locator.qualified_type.type); + VLOG(1) << "Type of argument '" << name << "': " << farg->typeName; + + farg->valid = true; + VLOG(1) << "Adding function arg address: " << farg; +} + +static bool handleInlinedFunction(const irequest &request, + std::shared_ptr funcDesc, + struct drgn_qualified_type &funcType, + Dwarf_Die &funcDie, + struct drgn_module *&module) { + VLOG(1) << "Function '" << funcDesc->symName << "' has been inlined"; + struct drgn_type_inlined_instances_iterator *iter = nullptr; + auto *err = drgn_type_inlined_instances_iterator_init(funcType.type, &iter); + if (err) { + LOG(ERROR) << "Error creating inlined instances iterator: " << err->message; + return false; + } + if (strcmp(drgn_type_parameters(funcType.type)[0].name, "this") == 0) { + funcDesc->isMethod = true; + } + auto index = funcDesc->getArgumentIndex(request.arg, false); + if (!index.has_value()) { + return false; + } + auto *argumentName = drgn_type_parameters(funcType.type)[index.value()].name; + struct drgn_type *inlinedInstance = nullptr; + bool foundInstance = false; + // The index at which the parameter was actually found in the inlined + // instance. This may differ from the index of the parameter in the function + // definition, as oftentimes as the result of compiler optimizations, some + // parameters will be omitted altogether from inlined instances. + size_t foundIndex = 0; + while (!foundInstance) { + err = drgn_type_inlined_instances_iterator_next(iter, &inlinedInstance); + if (err) { + LOG(ERROR) << "Error advancing inlined instances iterator: " + << err->message; + return false; + } + if (!inlinedInstance) { + LOG(ERROR) << "Could not find an inlined instance of this function " + "with the argument '" + << argumentName << "'"; + return false; + } + + auto numParameters = drgn_type_num_parameters(inlinedInstance); + auto *parameters = drgn_type_parameters(inlinedInstance); + for (size_t i = 0; i < numParameters; i++) { + if (strcmp(argumentName, parameters[i].name) == 0) { + foundInstance = true; + foundIndex = i; + break; + } + } + } + + if (foundIndex != index) { + // We patch the parameters of `inlinedInstance` such that + // each parameter is found at the index one would expect from + // the function definition, matching the representation of the + // abstract root. + auto targetParameter = drgn_type_parameters(inlinedInstance)[foundIndex]; + inlinedInstance->_private.num_parameters = + drgn_type_num_parameters(funcType.type); + // Allocating with `calloc` since `drgn` manages the lifetimes of its + // own structures, and it is written in C. + inlinedInstance->_private.parameters = (struct drgn_type_parameter *)calloc( + inlinedInstance->_private.num_parameters, + sizeof(*inlinedInstance->_private.parameters)); + inlinedInstance->_private.parameters[index.value()] = targetParameter; + } + + err = drgn_type_dwarf_die(inlinedInstance, &funcDie); + if (err) { + LOG(ERROR) << "Error obtaining DWARF DIE from type: " << err->message; + return false; + } + funcType.type = inlinedInstance; + module = inlinedInstance->_private.module; + return true; +} + +static std::optional> createFuncDesc( + struct drgn_program *prog, const irequest &request) { + VLOG(1) << "Creating function description for: " << request.func; + + Dwarf_Die funcDie; + struct drgn_qualified_type ft {}; + struct drgn_module *module = nullptr; + + if (auto *err = drgn_program_find_type_by_symbol_name( + prog, request.func.c_str(), &ft, &funcDie, &module)) { + LOG(ERROR) << "Error when finding type by symbol: " << err->code << " " + << err->message; + return std::nullopt; + } + + if (drgn_type_kind(ft.type) != DRGN_TYPE_FUNCTION) { + LOG(ERROR) << "Type corresponding to symbol '" << request.func + << "' is not a function"; + return std::nullopt; + } + + auto fd = std::make_shared(request.func); + + if (dwarf_func_inline(&funcDie) == 1) { + if (!handleInlinedFunction(request, fd, ft, funcDie, module)) { + return std::nullopt; + } + } + + ptrdiff_t offset = 0; + uintptr_t base = 0; + uintptr_t start = 0; + uintptr_t end = 0; + + while ((offset = dwarf_ranges(&funcDie, offset, &base, &start, &end)) > 0) { + fd->ranges.emplace_back(start, end); + } + + if (offset < 0) { + LOG(ERROR) << "Error while finding ranges of function: " + << dwarf_errmsg(dwarf_errno()); + return std::nullopt; + } + + auto retType = drgn_type_type(ft.type); + auto retTypeName = SymbolService::getTypeName(retType.type); + VLOG(1) << "Retval has type: " << retTypeName; + + if (!retTypeName.empty() && retTypeName != "void") { + /* + * I really can't figure out at the minute how to deduce from the DWARF + * which register is used for the return value. I don't think we can just + * assume it's 'rax' as according to the AMD64 ABI V1.0 Section 12.1.3 we + * can use 'rax', 'rdi, and I think it may be more complex than that. More + * investigation required. + * Moreover, we must fabricate a pointer type to the return type for the + * locator code to properly interpret the register's content. This WILL + * break for return-by-value instead of return-by-reference. But this kind + * of assumption is in-line we what we need to improve about return-value + * locating, so this will be good-enough for now. + * + * For now, fabricate a 'Retval' object for rax. + */ + fd->retval = std::make_shared(); + fd->retval->typeName = std::move(retTypeName); + fd->retval->valid = true; + } + + // Add params + bool isVariadic = false; + fd->arguments.reserve(drgn_type_num_parameters(ft.type)); + Dwarf_Die child; + int r = dwarf_child(&funcDie, &child); + + while (r == 0) { + switch (dwarf_tag(&child)) { + case DW_TAG_formal_parameter: + if (isVariadic) { + LOG(WARNING) << "Formal parameter after unspecified " + "parameters tag!"; + } + + parseFormalParam(child, module, prog, funcDie, fd); + break; + + case DW_TAG_unspecified_parameters: + if (isVariadic) { + VLOG(1) << "Multiple variadic parameters!"; + } + VLOG(1) << "Unspecified parameters tag"; + isVariadic = true; + break; + + default: + break; + } + r = dwarf_siblingof(&child, &child); + } + + if (r == -1) { + LOG(ERROR) << "Couldn't parse DIE children"; + } + + return fd; +} + +/* + * Locate the function descriptor from the function descriptor cache or create + * one if it doesn't exist. Just take the + * up front hit of looking everything up now. + */ +std::shared_ptr SymbolService::findFuncDesc(const irequest &request) { + if (auto it = funcDescs.find(request.func); it != end(funcDescs)) { + VLOG(1) << "Found funcDesc for " << request.func; + return it->second; + } + + struct drgn_program *drgnProg = getDrgnProgram(); + if (drgnProg == nullptr) { + return nullptr; + } + + auto fd = createFuncDesc(drgnProg, request); + if (!fd.has_value()) { + LOG(ERROR) << "Failed to create FuncDesc for " << request.func; + return nullptr; + } + + VLOG(1) << "findFuncDesc returning " << std::hex << fd.value()->symName; + funcDescs.emplace(request.func, fd.value()); + return fd.value(); +} + +std::shared_ptr SymbolService::findGlobalDesc( + const std::string &global) { + if (auto it = globalDescs.find(global); it != end(globalDescs)) { + VLOG(1) << "Found globalDesc for " << global; + return it->second; + } + + auto sym = locateSymbol(global); + if (!sym.has_value()) { + LOG(ERROR) << "Failed to get address for global " << global; + return nullptr; + } + + VLOG(1) << "locateGlobal: address of " << global << " " << std::hex + << sym->addr; + + struct drgn_program *drgnProg = getDrgnProgram(); + if (drgnProg == nullptr) { + return nullptr; + } + + auto gd = std::make_shared(global, sym->addr); + + struct drgn_object globalObj {}; + drgn_object_init(&globalObj, drgnProg); + BOOST_SCOPE_EXIT_ALL(&) { + drgn_object_deinit(&globalObj); + }; + + if (auto *err = drgn_program_find_object(drgnProg, global.c_str(), nullptr, + DRGN_FIND_OBJECT_ANY, &globalObj)) { + LOG(ERROR) << "Failed to lookup global variable '" << global + << "': " << err->code << " " << err->message; + + return nullptr; + } + + auto globalType = drgn_object_qualified_type(&globalObj); + gd->typeName = getTypeName(globalType.type); + + VLOG(1) << "findGlobalDesc returning " << std::hex << gd; + globalDescs.emplace(global, gd); + return gd; +} + +std::string SymbolService::getTypeName(struct drgn_type *type) { + if (drgn_type_kind(type) == DRGN_TYPE_POINTER) { + type = drgn_type_type(type).type; + } + return OICodeGen::typeToName(type); +} diff --git a/src/SymbolService.h b/src/SymbolService.h new file mode 100644 index 0000000..5b82732 --- /dev/null +++ b/src/SymbolService.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Descs.h" + +namespace fs = std::filesystem; + +struct drgn_program; +struct irequest; + +struct SymbolInfo { + uint64_t addr; + uint64_t size; +}; + +class SymbolService { + public: + SymbolService(std::variant); + ~SymbolService(); + + struct drgn_program *getDrgnProgram(); + + std::optional locateBuildID(); + std::optional locateSymbol(const std::string &); + + std::shared_ptr findFuncDesc(const irequest &); + std::shared_ptr findGlobalDesc(const std::string &); + static std::string getTypeName(struct drgn_type *); + + std::unordered_map> funcDescs; + std::unordered_map> globalDescs; + + void setHardDisableDrgn(bool val) { + hardDisableDrgn = val; + } + + private: + std::variant target{0}; + struct drgn_program *prog{nullptr}; + + std::vector> executableAddrs{}; + bool hardDisableDrgn = false; +}; diff --git a/src/Syscall.h b/src/Syscall.h new file mode 100644 index 0000000..5ee37e3 --- /dev/null +++ b/src/Syscall.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace { +template +struct StringLiteral { + constexpr StringLiteral(const char (&str)[N]) { + std::copy_n(str, N, value); + } + + char value[N]; +}; +} // namespace + +/* + * The Syscall structure describes a `syscall(2)` in a generic manner. + * The struct is used by `OIDebugger::remoteSyscall` to define the return type + * and statically check the number of arguments being passed. Currently, I don't + * know how to use the _Args to also statically check the type of the arguments. + * In the meantime, I can use the size of _Args to do a simple count check. + */ +template +struct Syscall { + /* User friendly syscall name */ + static constexpr auto Name = _Name.value; + + /* The syscall's number (see ) */ + static constexpr unsigned long SysNum = _SysNum; + + /* The syscall's return type */ + using RetType = _RetType; + + /* The number of arguments the syscall takes */ + static constexpr size_t ArgsCount = sizeof...(_Args); + static_assert(ArgsCount <= 6, + "X64 syscalls support a maximum of 6 arguments"); +}; + +/* + * The list of syscalls we want to be able to use on the remote process. + * The types passed to `struct Syscall` come directly from `man 2 `. + * Don't hesitate to expand this list if you need more syscalls! + */ +using SysOpen = Syscall<"open", SYS_open, int, const char *, int, mode_t>; +using SysClose = Syscall<"close", SYS_close, int, int>; +using SysFsync = Syscall<"fsync", SYS_fsync, int, int>; + +using SysMmap = + Syscall<"mmap", SYS_mmap, void *, void *, size_t, int, int, int, off_t>; +using SysMunmap = Syscall<"munmap", SYS_munmap, int, void *, size_t>; diff --git a/src/TimeUtils.h b/src/TimeUtils.h new file mode 100644 index 0000000..bfb333d --- /dev/null +++ b/src/TimeUtils.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include + +using time_hr = std::chrono::high_resolution_clock; + +template +auto time_ns(Duration const &d) { + return std::chrono::duration_cast(d).count(); +} diff --git a/src/TrapInfo.h b/src/TrapInfo.h new file mode 100644 index 0000000..9ecb05d --- /dev/null +++ b/src/TrapInfo.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +#include +#include + +#include "Metrics.h" +#include "OICompiler.h" + +extern "C" { +#include +} + +/* + * Breakpoint traps (INT3) instructions are the primary mechanism used to + * transfer control from the traced process to the debugger. There are several + * variants of traps : + * OID_TRAP_JITCODERET: transfers control from JIT'd code sequences and + * other setup operations. + * OID_TRAP_VECT_ENTRY: transfers control from function entry sites. + * OID_TRAP_VECT_ENTRYRET: transfer control from function entry sites as + * part of function return tracing. Used to capture + * function argument parameters for use in function + * return introspection. + * OID_TRAP_VECT_RET: transfers control from function return sites. + * + * The differing types of re-vectoring operations share a lot of state but + * differ in a few ways. For example, we don't need to stash the original + * instructions for a OID_TRAP_JITCODERET sequence. + * + * Note that we have two maps of trapInfo objects: + * 'activeTraps': a mapping of active breakpoints in the target process + * 'threadTrapState': a mapping of thread to breakpoint. Used when a thread + * is executing a OID_TRAP_JITCODERET sequence as a result of a OID_TRAP_VECT + * breakpoint (i.e., executing JIT'd measurement code as a result of of a + * thread vectoring in from an instrumented application). + */ +enum trapType { + OID_TRAP_JITCODERET = 0, + OID_TRAP_VECT_ENTRY = 1, + OID_TRAP_VECT_ENTRYRET = 2, + OID_TRAP_VECT_RET = 3 +}; + +const uint64_t GLOBAL_VARIABLE_TRAP_ADDR = 0xfeedfacefeedface; + +class trapInfo { + public: + trapType trapKind{OID_TRAP_JITCODERET}; + + /* The text address of the breakpoint trap (used for id of trap) */ + uintptr_t trapAddr{}; + + /* + * Relocated memory location in prologue of target object - only used for + * OID_TRAP_VECT* traps. + */ + uintptr_t prologueObjAddr{}; + + /* + * If this is a OID_TRAP_JITCODERET trap and this is true then vector the + * thread back. + */ + bool fromVect{false}; + + /* + * For function entry traps we need to stash the first 8 bytes of text. + * (NOTE: we actually only need 1 but ptrace() minimum unit is 8 bytes. + */ + union { + unsigned long origText{0}; + uint8_t origTextBytes[8]; + }; + + /* + * For OID_TRAP_VECT_ENTRYRET traps we construct the patched version + * of all traps before enabling them. The following 8 bytes just make + * the code a bit cleaner for that case and are not used when processing + * traps. + */ + union { + unsigned long patchedText{0}; + uint8_t patchedTextBytes[8]; + }; + + /* Populated with registers of interrupted thread on entry to trap */ + struct user_regs_struct savedRegs; + + /* Floating point registers */ + struct user_fpregs_struct savedFPregs; + + /* This is just temp while we implement proper register/argument support */ + std::vector> args; + + /* + * Instructions that have been patched over must be replayed so that the + * effects are observable in the thread. To do this we stash the original + * instruction in the target process at this address which is used as a + * single step target for execution. + */ + uintptr_t replayInstAddr{}; + + ObjectIntrospection::Metrics::Tracing lifetime{"trap"}; + + trapInfo() = default; + trapInfo(trapType t, uint64_t ta, uint64_t po = 0, bool fv = false) + : trapKind{t}, trapAddr{ta}, prologueObjAddr{po}, fromVect{fv} { + } +}; + +inline std::ostream &operator<<(std::ostream &out, const trapInfo &t) { + static const char *trapTypeDescs[] = { + "JIT Code Return", // OID_TRAP_JITCODERET + "Vector Entry", // OID_TRAP_VECT_ENTRY + "Vector Entry Return", // OID_TRAP_VECT_ENTRYRET + "Vector Return", // OID_TRAP_VECT_RET + }; + return out << "Trap " << trapTypeDescs[t.trapKind] << " @" + << (void *)t.trapAddr; +} diff --git a/src/TreeBuilder.cpp b/src/TreeBuilder.cpp new file mode 100644 index 0000000..26de16b --- /dev/null +++ b/src/TreeBuilder.cpp @@ -0,0 +1,894 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 "TreeBuilder.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ContainerInfo.h" +#include "Metrics.h" +#include "OICodeGen.h" +#include "PaddingHunter.h" +#include "rocksdb/db.h" +#include "rocksdb/options.h" + +extern "C" { +#include +#include +} + +TreeBuilder::TreeBuilder(Config c) : config{std::move(c)} { + buffer = std::make_unique(); + + auto testdbPath = "/tmp/testdb_" + std::to_string(getpid()); + if (auto status = rocksdb::DestroyDB(testdbPath, {}); !status.ok()) { + LOG(FATAL) << "RocksDB error while destroying database: " + << status.ToString(); + } + + const int twoMinutes = 120; + rocksdb::Options options; + options.compression = rocksdb::kZSTD; + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + options.stats_dump_period_sec = twoMinutes; + options.PrepareForBulkLoad(); + options.OptimizeForSmallDb(); + + if (auto status = rocksdb::DB::Open(options, testdbPath, &db); !status.ok()) { + LOG(FATAL) << "RocksDB error while opening database: " << status.ToString(); + } +} + +struct TreeBuilder::Variable { + struct drgn_type *type; + std::string_view name; + std::string typePath; + std::optional isset = std::nullopt; + bool isStubbed = false; +}; + +struct TreeBuilder::DBHeader { + /** + * Version of the database schema. See TreeBuilder.h for more info. + */ + Version version; + + /** + * List of IDs corresponding to the root of the probed objects. + */ + std::vector rootIDs; + + MSGPACK_DEFINE_ARRAY(version, rootIDs) +}; + +struct TreeBuilder::Node { + struct ContainerStats { + /** + * The number of elements currently present in the container + * (e.g. `std::vector::size()`). + */ + size_t length; + /** + * The maximum number of elements the container can + * currently hold (e.g. `std::vector::capacity()`). + */ + size_t capacity; + /** + * The static size (see comment for `staticSize` below for clarification on + * what this means) of each element in a container. For example, if this + * node corresponds to a `std::vector` then `elementStaticSize` + * would be `sizeof(int)`. + */ + size_t elementStaticSize; + MSGPACK_DEFINE_ARRAY(length, capacity, elementStaticSize) + }; + + /** + * The unique identifier for this node, used as the key for this + * node's entry in RocksDB. + */ + NodeID id; + /** + * Roughly corresponds to the name you would use to refer to this node in + * the code (e.g. variable name, member name, etc.). In some cases there is + * no meaningful name (e.g. the elements of a vector, the node behind a + * `typedef`) and this is left empty. + */ + std::string_view name{}; + /** + * The type of this node, as it would be written in the code + * (e.g. `std::vector`, `float`, `MyStruct`). + */ + std::string typeName{}; + std::string typePath{}; + bool isTypedef{}; + /** + * The compile-time-determinable size (i.e. memory footprint measured in + * bytes) of this node, essentially corresponding to `sizeof(TYPE)`. + * Just like the semantics of `sizeof`, this is inherently inclusive of the + * type's members (if it is a `struct`, `class`, or `union`). + */ + size_t staticSize{}; + /** + * The size (i.e. memory usage measured in bytes) of the dynamically + * allocated data used by this node (e.g. the heap-allocated memory + * associated with a `std::vector`). This includes the `dynamicSize` of all + * children (whether they be `struct`/`class` members, or the elements of a + * container). + */ + size_t dynamicSize{}; + std::optional paddingSavingsSize{std::nullopt}; + std::optional pointer{std::nullopt}; + std::optional containerStats{std::nullopt}; + /** + * Range of this node's children (start is inclusive, end is exclusive) + * + * If this node represents a container, `children` contains all + * of the container's elements. + * If this node represents a `struct` or `class`, `children` + * contains all of its members. + * If this node is a `typedef` or a pointer, `children` should contain a + * single entry corresponding to the referenced type. + */ + std::optional> children{std::nullopt}; + + std::optional isset{std::nullopt}; + + MSGPACK_DEFINE_ARRAY(id, name, typeName, typePath, isTypedef, staticSize, + dynamicSize, paddingSavingsSize, containerStats, pointer, + children, isset) +}; + +TreeBuilder::~TreeBuilder() { + /* FB: Remove error IDs, Strobelight doesn't handle them yet */ + std::erase(rootIDs, ERROR_NODE_ID); + + /* + * Now that all the Nodes have been inserted in the DB, + * we can insert the DBHeader with the proper list of rootIDs. + */ + const DBHeader header{.version = VERSION, .rootIDs = std::move(rootIDs)}; + auto serializedHeader = serialize(header); + + rocksdb::WriteOptions options{}; + options.disableWAL = true; + if (auto status = + db->Put(options, std::to_string(ROOT_NODE_ID), serializedHeader); + !status.ok()) { + LOG(ERROR) << "RocksDB error while writing DBHeader: " << status.ToString(); + } + + if (auto status = db->Close(); !status.ok()) { + LOG(ERROR) << "RocksDB error while closing database: " << status.ToString(); + } + + delete db; +} + +bool TreeBuilder::emptyOutput() const { + return std::ranges::all_of(rootIDs, + [](auto &id) { return id == ERROR_NODE_ID; }); +} + +void TreeBuilder::build(const std::vector &data, + const std::string &argName, struct drgn_type *type, + const TypeHierarchy &typeHierarchy) { + th = &typeHierarchy; + oidData = &data; + + pointers.clear(); + oidDataIndex = 3; // HACK: OID's first 3 outputs are dummy 0s + + ObjectIntrospection::Metrics::Tracing _("build_tree"); + VLOG(1) << "Building tree..."; + + { + auto &rootID = rootIDs.emplace_back(nextNodeID++); + + try { + // The first value is the address of the root object + pointers.insert(next()); + process(rootID, {.type = type, .name = argName, .typePath = argName}); + } catch (...) { + // Mark the failure using the error node ID + rootID = ERROR_NODE_ID; + throw; + } + } + + VLOG(1) << "Finished building tree"; + rocksdb::CompactRangeOptions opts; + rocksdb::Status s = db->CompactRange(opts, nullptr, nullptr); + if (!s.ok()) { + LOG(FATAL) << "RocksDB error while compacting: " << s.ToString(); + } + VLOG(1) << "Finished compacting db"; + + // Were all object sizes consumed? + if (oidDataIndex != oidData->size()) { + LOG(WARNING) << "WARNING: some object sizes not consumed;" + << "object tree may be inaccurate. " + << "reported: " << oidData->size() << " consumed " + << oidDataIndex; + } else { + VLOG(1) << "Consumed all object sizes: " << oidDataIndex; + } + + th = nullptr; + oidData = nullptr; +} + +void TreeBuilder::dumpJson() { + if (!config.jsonPath.has_value()) { + LOG(ERROR) << "No output path was provided for JSON"; + return; + } + + std::ofstream output(*config.jsonPath); + output << '['; + for (auto rootID : rootIDs) { + if (rootID == ERROR_NODE_ID) { + // On error, output an empty object to maintain offsets + output << "{},"; + } else { + JSON(rootID, output); + output << ','; + } + } + + /* Remove the trailing comma */ + if (!rootIDs.empty()) { + output.seekp(-1, std::ios_base::cur); + } + + output << "]\n"; // Text files should end with a newline per POSIX + + VLOG(1) << "Finished writing JSON to disk"; +} + +void TreeBuilder::setPaddedStructs( + std::map *_paddedStructs) { + this->paddedStructs = _paddedStructs; +} + +static std::string drgnTypeToName(struct drgn_type *type) { + if (type->_private.program != nullptr) { + return OICodeGen::typeToName(type); + } + + return type->_private.oi_name ? type->_private.oi_name : ""; +} + +static struct drgn_error *drgnTypeSizeof(struct drgn_type *type, + uint64_t *ret) { + static struct drgn_error incompleteTypeError = { + .code = DRGN_ERROR_TYPE, + .needs_destroy = false, + .errnum = 0, + .path = NULL, + .address = 0, + .message = (char *)"cannot get size of incomplete type", + }; + + if (drgn_type_kind(type) == DRGN_TYPE_FUNCTION) { + *ret = sizeof(uintptr_t); + return nullptr; + } + + if (type->_private.program != nullptr) { + return drgn_type_sizeof(type, ret); + } + + // If type has no size, report an error to trigger a sizeMap lookup + if (type->_private.oi_size == + std::numeric_limits_private.size)>::max()) { + return &incompleteTypeError; + } + + *ret = type->_private.oi_size; + return nullptr; +} + +uint64_t TreeBuilder::getDrgnTypeSize(struct drgn_type *type) { + uint64_t size = 0; + struct drgn_error *err = drgnTypeSizeof(type, &size); + BOOST_SCOPE_EXIT(err) { + drgn_error_destroy(err); + } + BOOST_SCOPE_EXIT_END + if (err == nullptr) { + return size; + } + + std::string typeName = drgnTypeToName(type); + for (auto &[typeName2, size2] : th->sizeMap) + if (typeName.starts_with(typeName2)) + return size2; + + if (typeName.starts_with("basic_string, " + "std::allocator >")) { + return sizeof(std::string); + } + + throw std::runtime_error("Failed to get size: " + std::to_string(err->code) + + " " + err->message); +} + +uint64_t TreeBuilder::next() { + if (oidDataIndex >= oidData->size()) { + throw std::runtime_error("Unexpected end of data"); + } + VLOG(3) << "next = " << (void *)(*oidData)[oidDataIndex]; + return (*oidData)[oidDataIndex++]; +} + +bool TreeBuilder::isContainer(const Variable &variable) { + return th->containerTypeMap.contains(variable.type) || + (drgn_type_kind(variable.type) == DRGN_TYPE_ARRAY && + drgn_type_length(variable.type) > 0); +} + +bool TreeBuilder::isPrimitive(struct drgn_type *type) { + while (drgn_type_kind(type) == DRGN_TYPE_TYPEDEF) { + auto entry = th->typedefMap.find(type); + if (entry == th->typedefMap.end()) + return false; + type = entry->second; + } + return drgn_type_primitive(type) != DRGN_NOT_PRIMITIVE_TYPE; +} + +bool TreeBuilder::shouldProcess(uintptr_t pointer) { + if (pointer == 0U) { + return false; + } + + auto [_, unprocessed] = pointers.emplace(pointer); + return unprocessed; +} + +static std::string_view drgnKindStr(struct drgn_type *type) { + auto kind = OICodeGen::drgnKindStr(type); + // -1 is for the null terminator + kind.remove_prefix(sizeof("DRGN_TYPE_") - 1); + return kind; +} + +TreeBuilder::Node TreeBuilder::process(NodeID id, Variable variable) { + Node node{ + .id = id, + .name = variable.name, + .typeName = drgnTypeToName(variable.type), + .typePath = std::move(variable.typePath), + .staticSize = getDrgnTypeSize(variable.type), + .isset = variable.isset, + }; + VLOG(2) << "Processing node [" << id << "] (name: '" << variable.name + << "', typeName: '" << node.typeName + << "', kind: " << drgnKindStr(variable.type) << ")" + << (variable.isStubbed ? " STUBBED" : "") + << (th->knownDummyTypeList.contains(variable.type) ? " DUMMY" : ""); + if (!variable.isStubbed) { + switch (drgn_type_kind(variable.type)) { + case DRGN_TYPE_POINTER: + if (config.chaseRawPointers) { + // Pointers to incomplete types are stubbed out + // See OICodeGen::enumeratePointerType + if (th->knownDummyTypeList.contains(variable.type)) { + break; + } + + auto entry = th->pointerToTypeMap.find(variable.type); + if (entry != th->pointerToTypeMap.end()) { + auto innerTypeKind = drgn_type_kind(entry->second); + if (innerTypeKind != DRGN_TYPE_FUNCTION) { + node.pointer = next(); + if (innerTypeKind == DRGN_TYPE_VOID || + !shouldProcess(*node.pointer)) { + break; + } + } + auto childID = nextNodeID++; + auto child = process(childID, Variable{entry->second, "", ""}); + node.children = {childID, childID + 1}; + node.dynamicSize = child.staticSize + child.dynamicSize; + } + } + break; + case DRGN_TYPE_TYPEDEF: { + const static boost::regex standardIntegerRegex{ + "((s?size)|(u?int(_fast|_least)?(8|16|32|64|128|ptr))|(ptrdiff))_" + "t"}; + // We don't expand typedefs for well-known integer types from `stdint.h` + // to prevent our output from being extremely verbose. We treat them as + // if they are primitives directly (hence this check coming *before* we + // set `node.isTypedef`). + if (boost::regex_match(node.typeName, standardIntegerRegex)) { + break; + } + node.isTypedef = true; + auto entry = th->typedefMap.find(variable.type); + if (entry != th->typedefMap.end()) { + auto childID = nextNodeID++; + auto child = process(childID, Variable{entry->second, "", ""}); + node.children = {childID, childID + 1}; + node.dynamicSize = child.dynamicSize; + } + } break; + case DRGN_TYPE_CLASS: + case DRGN_TYPE_STRUCT: + case DRGN_TYPE_ARRAY: + if (th->knownDummyTypeList.contains(variable.type)) { + break; + } else if (isContainer(variable)) { + processContainer(variable, node); + } else { + auto entry = th->classMembersMap.find(variable.type); + if (entry == th->classMembersMap.end() || entry->second.empty()) { + break; + } + + const auto &members = entry->second; + node.children = {nextNodeID, nextNodeID + members.size()}; + nextNodeID += members.size(); + auto childID = node.children->first; + + bool captureThriftIsset = + th->thriftIssetStructTypes.contains(variable.type); + + for (std::size_t i = 0; i < members.size(); i++) { + std::optional isset; + if (captureThriftIsset && i < members.size() - 1) { + // Retrieve isset value for each member variable, except Thrift's + // __isset field, which we assume comes last. + // A value of -1 indicates a non-optional field for which we + // don't record an isset value. + auto val = next(); + if (val != (uint64_t)-1) { + isset = val; + } + } + const auto &member = members[i]; + auto child = + process(childID++, + Variable{member.type, member.member_name, + member.member_name, isset, member.isStubbed}); + node.dynamicSize += child.dynamicSize; + } + } + break; + default: + // The remaining types are all described entirely by their static size, + // and hence need no special handling. + break; + } + + if (config.genPaddingStats) { + auto entry = paddedStructs->find(node.typeName); + if (entry != paddedStructs->end()) { + entry->second.instancesCnt++; + node.paddingSavingsSize = entry->second.savingSize; + } + } + } + + rocksdb::WriteOptions options{}; + options.disableWAL = true; + auto status = db->Put(options, std::to_string(node.id), serialize(node)); + if (!status.ok()) { + throw std::runtime_error("RocksDB error while inserting node [" + + std::to_string(node.id) + + "]: " + status.ToString()); + } + return node; +} + +void TreeBuilder::processContainer(const Variable &variable, Node &node) { + VLOG(1) << "Processing container [" << node.id << "] of type '" + << node.typeName << "'"; + ContainerTypeEnum kind = UNKNOWN_TYPE; + std::vector elementTypes; + + if (drgn_type_kind(variable.type) == DRGN_TYPE_ARRAY) { + kind = ARRAY_TYPE; + struct drgn_type *arrayElementType = nullptr; + size_t numElems = 0; + OICodeGen::getDrgnArrayElementType(variable.type, &arrayElementType, + numElems); + assert(numElems > 0); + elementTypes.push_back( + drgn_qualified_type{arrayElementType, (enum drgn_qualifiers)(0)}); + } else { + auto entry = th->containerTypeMap.find(variable.type); + if (entry == th->containerTypeMap.end()) { + throw std::runtime_error( + "Could not find container information for type with name '" + + node.typeName + "'"); + } + + auto &[containerInfo, templateTypes] = entry->second; + kind = containerInfo.ctype; + for (const auto &tt : templateTypes) { + elementTypes.push_back(tt); + } + } + + /** + * Some containers (conditionally) store their contents *directly* inside + * themselves (as opposed to having a pointer to heap-allocated memory). + * `std::pair` and `std::array` are two trivial examples, but some types vary + * whether their contents are stored inline or externally depending on + * runtime conditions (usually the number of elements currently present in + * the container). + */ + bool contentsStoredInline = false; + + // Initialize, then take a reference to the underlying value for convenience + // so that we don't have to dereference the optional every time we want to use + // it. + auto &containerStats = + node.containerStats.emplace(Node::ContainerStats{0, 0, 0}); + + for (auto &type : elementTypes) { + containerStats.elementStaticSize += getDrgnTypeSize(type.type); + } + + switch (kind) { + case OPTIONAL_TYPE: + contentsStoredInline = true; + containerStats.length = containerStats.capacity = 1; + if (next() == 0U) { + containerStats.length = 0; + return; + } + break; + case FOLLY_OPTIONAL_TYPE: + // TODO: Not sure why we are capturing pointer for folly::Optional but + // not std::optional. Both are supposed to store data inline. + contentsStoredInline = true; + node.pointer = next(); + containerStats.length = containerStats.capacity = 1; + if (*node.pointer == 0) { + containerStats.length = 0; + return; + } + break; + case SHRD_PTR_TYPE: + case UNIQ_PTR_TYPE: + node.pointer = next(); + containerStats.length = *node.pointer ? 1 : 0; + containerStats.capacity = 1; + if (!shouldProcess(*node.pointer)) { + return; + } + break; + case TRY_TYPE: + case REF_WRAPPER_TYPE: + node.pointer = next(); + containerStats.length = containerStats.capacity = 1; + if (!shouldProcess(*node.pointer)) { + return; + } + break; + case CONTAINER_ADAPTER_TYPE: { + node.pointer = next(); + + // Copy the underlying container's sizes and stats directly into this + // container adapter + node.children = {nextNodeID, nextNodeID + 1}; + nextNodeID += 1; + auto childID = node.children->first; + // elementTypes is only populated with the underlying container type for + // container adapters + auto containerType = elementTypes[0]; + auto child = process( + childID++, {.type = containerType.type, + .name = "", + .typePath = drgnTypeToName(containerType.type) + "[]"}); + + node.dynamicSize = child.dynamicSize; + node.containerStats = child.containerStats; + return; + } + case STD_VARIANT_TYPE: { + containerStats.length = containerStats.capacity = 1; + containerStats.elementStaticSize = 0; + for (auto &type : elementTypes) { + auto paramSize = getDrgnTypeSize(type.type); + containerStats.elementStaticSize = + std::max(containerStats.elementStaticSize, paramSize); + } + + node.dynamicSize = 0; + + // When a std::variant is valueless_by_exception, its index will be + // std::variant_npos (i.e. 0xffffffffffffffff). + // + // libstdc++ and libc++ both optimise the storage required for# + // std::variant's index value by using fewer than 8-bytes when possible. + // e.g. for a std::variant, only three index values are required: + // one each for A and B and one for variant_npos. variant_npos may be + // represented internally by 0xff and only converted back to + // 0xffffffffffffffff when index() is called. + // + // However, this conversion may be optimised away in the target process, + // so we need to treat any invalid index as variant_npos. + if (auto index = next(); index < elementTypes.size()) { + // Recurse only into the type of the template parameter which + // is currently stored in this variant + node.children = {nextNodeID, nextNodeID + 1}; + nextNodeID += 1; + auto childID = node.children->first; + + auto elementType = elementTypes[index]; + auto child = process( + childID++, {.type = elementType.type, + .name = "", + .typePath = drgnTypeToName(elementType.type) + "[]"}); + + node.dynamicSize = child.dynamicSize; + } + return; + } + case PAIR_TYPE: + contentsStoredInline = true; + containerStats.length = containerStats.capacity = 1; + break; + case SEQ_TYPE: + case MICROLIST_TYPE: + case SORTED_VEC_SET_TYPE: + case FEED_QUICK_HASH_SET: + case FEED_QUICK_HASH_MAP: + case FB_HASH_MAP_TYPE: + case FB_HASH_SET_TYPE: + case MAP_SEQ_TYPE: + case FOLLY_SMALL_HEAP_VECTOR_MAP: + case REPEATED_FIELD_TYPE: + node.pointer = next(); + containerStats.capacity = next(); + containerStats.length = next(); + break; + case LIST_TYPE: + node.pointer = next(); + containerStats.length = containerStats.capacity = next(); + break; + case FOLLY_IOBUFQUEUE_TYPE: + node.pointer = next(); + containerStats.length = containerStats.capacity = 0; + if (!shouldProcess(*node.pointer)) { + return; + } + // Fallthrough to the IOBuf data if we have a valid pointer + [[fallthrough]]; + case FOLLY_IOBUF_TYPE: + containerStats.capacity = next(); + containerStats.length = next(); + break; + case FB_STRING_TYPE: + node.pointer = next(); + containerStats.capacity = next(); + containerStats.length = next(); + // If this string's data is potentially shared (cutoff for sharing is 255) + if (containerStats.capacity >= 255) { + // Contents aren't actually stored inline in this case, + // but we set this to `true` so that we don't double-count + // this string's data if we have seen it before. + contentsStoredInline = !shouldProcess(*node.pointer); + } else { + // No sense in recording the pointer value if the string isn't + // potentially shared + node.pointer.reset(); + // Account for Small String Optimization (SSO) + const int fbStringSsoCutOff = 23; + constexpr size_t ssoCutoff = fbStringSsoCutOff; + contentsStoredInline = containerStats.capacity <= ssoCutoff; + } + break; + case STRING_TYPE: + containerStats.capacity = next(); + containerStats.length = next(); + // Account for Small String Optimization (SSO) + // LLVM libc++: sizeof(string) = 24, SSO cutoff = 22 + // GNU libstdc++: sizeof(string) = 32, SSO cutoff = 15 + { + const int llvmSizeOf = 24; + const int llvmSsoCutOff = 22; + [[maybe_unused]] const int gnuSizeOf = 32; + const int gnuSsoCutOff = 15; + assert(node.staticSize == llvmSizeOf || node.staticSize == gnuSizeOf); + size_t ssoCutoff = + node.staticSize == llvmSizeOf ? llvmSsoCutOff : gnuSsoCutOff; + contentsStoredInline = containerStats.capacity <= ssoCutoff; + } + break; + case CAFFE2_BLOB_TYPE: + // This is a weird one, need to ask why we just overwite size like this + node.dynamicSize = next(); + return; + case ARRAY_TYPE: + contentsStoredInline = true; + containerStats.length = containerStats.capacity = next(); + break; + case SMALL_VEC_TYPE: { + size_t maxInline = next(); + containerStats.capacity = next(); + containerStats.length = next(); + contentsStoredInline = containerStats.capacity <= maxInline; + } break; + case BOOST_BIMAP_TYPE: + // TODO: Hard to know the overhead of boost bimap. It isn't documented in + // the boost docs. Need to look closer at the implementation. + containerStats.length = containerStats.capacity = next(); + break; + case SET_TYPE: + case STD_MAP_TYPE: + // Account for node overhead + containerStats.elementStaticSize += next(); + containerStats.length = containerStats.capacity = next(); + break; + case UNORDERED_SET_TYPE: + case STD_UNORDERED_MAP_TYPE: { + // Account for node overhead + containerStats.elementStaticSize += next(); + size_t bucketCount = next(); + // Both libc++ and libstdc++ define buckets as an array of raw pointers + node.dynamicSize += bucketCount * sizeof(void *); + containerStats.length = containerStats.capacity = next(); + } break; + case F14_MAP: + case F14_SET: + // F14 maps/sets don't actually store their contents inline, but the + // intention of setting this to `true` is to skip the usual calculation + // performed to determine `node.dynamicSize`, since F14 maps very + // conveniently provide a `getAllocatedMemorySize()` method which we can + // use instead. + contentsStoredInline = true; + node.dynamicSize += next(); + containerStats.capacity = next(); + containerStats.length = next(); + break; + case RADIX_TREE_TYPE: + case MULTI_MAP_TYPE: + case BY_MULTI_QRT_TYPE: + containerStats.length = containerStats.capacity = next(); + break; + case THRIFT_ISSET_TYPE: + // Dummy container + containerStats.elementStaticSize = 0; + break; + default: + throw std::runtime_error("Unknown container (type was 0x" + + std::to_string(kind) + ")"); + break; + } + + if (!contentsStoredInline) { + node.dynamicSize += + containerStats.elementStaticSize * containerStats.capacity; + } + + // A cutoff value used to sanity-check our results. If a container + // is larger than this, chances are that we've read uninitialized data, + // or there's a bug in Codegen. + constexpr size_t CONTAINER_SIZE_THRESHOLD = 1ULL << 38; + if (containerStats.elementStaticSize * containerStats.capacity >= + CONTAINER_SIZE_THRESHOLD) { + throw std::runtime_error( + "Container size exceeds threshold, this is likely due to reading " + "uninitialized data in the target process"); + } + if (std::ranges::all_of( + elementTypes.cbegin(), elementTypes.cend(), + [this](auto &type) { return isPrimitive(type.type); })) { + VLOG(1) + << "Container [" << node.id + << "] contains only primitive types, skipping processing its members"; + return; + } + + auto numChildren = containerStats.length * elementTypes.size(); + if (numChildren == 0) { + VLOG(1) << "Container [" << node.id << "] has no children"; + return; + } + node.children = {nextNodeID, nextNodeID + numChildren}; + VLOG(1) << "Container [" << node.id << "]'s children cover range [" + << node.children->first << ", " << node.children->second << ")"; + nextNodeID += numChildren; + auto childID = node.children->first; + for (size_t i = 0; i < containerStats.length; i++) { + for (auto &type : elementTypes) { + auto child = + process(childID++, {.type = type.type, + .name = "", + .typePath = drgnTypeToName(type.type) + "[]"}); + node.dynamicSize += child.dynamicSize; + } + } +} + +template +std::string_view TreeBuilder::serialize(const T &data) { + buffer->clear(); + msgpack::pack(*buffer, data); + // It is *very* important that we construct the `std::string_view` with an + // explicit length, since `buffer->data()` may contain null bytes. + return std::string_view(buffer->data(), buffer->size()); +} + +void TreeBuilder::JSON(NodeID id, std::ofstream &output) { + std::string data; + auto status = db->Get(rocksdb::ReadOptions(), std::to_string(id), &data); + if (!status.ok()) { + throw std::runtime_error("RocksDB error while reading node [" + + std::to_string(id) + "]: " + status.ToString()); + } + + Node node; + msgpack::unpack(data.data(), data.size()).get().convert(node); + // Remove all backslashes to ensure the output is valid JSON + std::replace(node.typePath.begin(), node.typePath.end(), '\\', ' '); + std::replace(node.typeName.begin(), node.typeName.end(), '\\', ' '); + output << "{"; + output << "\"name\":\"" << node.name << "\","; + output << "\"typePath\":\"" << node.typePath << "\","; + output << "\"typeName\":\"" << node.typeName << "\","; + output << "\"isTypedef\":" << (node.isTypedef ? "true" : "false") << ","; + output << "\"staticSize\":" << node.staticSize << ","; + output << "\"dynamicSize\":" << node.dynamicSize; + if (node.paddingSavingsSize.has_value()) { + output << ","; + output << "\"paddingSavingsSize\":" << *node.paddingSavingsSize; + } + if (node.pointer.has_value()) { + output << ","; + output << "\"pointer\":" << *node.pointer; + } + if (node.containerStats.has_value()) { + output << ","; + output << "\"length\":" << node.containerStats->length << ","; + output << "\"capacity\":" << node.containerStats->capacity << ","; + output << "\"elementStaticSize\":" + << node.containerStats->elementStaticSize; + } + if (node.isset.has_value()) { + output << ","; + output << "\"isset\":" << (*node.isset ? "true" : "false"); + } + if (node.children.has_value()) { + output << ","; + output << "\"members\":["; + auto [childIDStart, childIDEnd] = *node.children; + assert(childIDStart < childIDEnd); + // Trailing commas are disallowed in JSON, so we pull + // out the first iteration of the loop. + JSON(childIDStart, output); + for (auto childID = childIDStart + 1; childID < childIDEnd; childID++) { + output << ","; + JSON(childID, output); + } + output << "]"; + } + output << "}"; +} diff --git a/src/TreeBuilder.h b/src/TreeBuilder.h new file mode 100644 index 0000000..3a71d89 --- /dev/null +++ b/src/TreeBuilder.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "Common.h" + +// The rocksdb includes are extremely heavy and bloat compile times, +// so we just forward-declare `DB` to avoid making other compile units +// pay the cost of including the relevant headers. +namespace rocksdb { +class DB; +} + +// Forward declared, comes from PaddingInfo.h +struct PaddingInfo; + +class TreeBuilder { + public: + struct Config { + // Don't set default values for the config so the user gets + // an "unitialized field" warning if he missed any. + bool logAllStructs; + bool chaseRawPointers; + bool genPaddingStats; + bool dumpDataSegment; + std::optional jsonPath; + }; + + TreeBuilder(Config); + ~TreeBuilder(); + + void build(const std::vector &, const std::string &, + struct drgn_type *, const TypeHierarchy &); + void dumpJson(); + void setPaddedStructs(std::map *paddedStructs); + bool emptyOutput() const; + + private: + typedef uint64_t Version; + typedef uint64_t NodeID; + struct DBHeader; + struct Node; + struct Variable; + + const TypeHierarchy *th = nullptr; + const std::vector *oidData = nullptr; + std::map *paddedStructs = nullptr; + + /* + * The RocksDB output needs versioning so they are imported correctly in + * Scuba. Version 1 had no concept of versioning and no header. + * We currently are at version 2.1: + * - Introduce the Error ID at index 1023, but don't output it + * Changelog v2: + * - Introduce the DBHeader at index 0 + * - Introduce the versioning + * - Handle multiple root_ids, to import multiple objects in Scuba + */ + static constexpr Version VERSION = 2; + static constexpr NodeID ROOT_NODE_ID = 0; + static constexpr NodeID ERROR_NODE_ID = 1023; + static constexpr NodeID FIRST_NODE_ID = 1024; + + /* + * The first 1024 IDs are reserved for future use. + * ID 0: DBHeader + * ID 1023: Error - an error occured while TreeBuilding + */ + NodeID nextNodeID = FIRST_NODE_ID; + + const Config config{}; + size_t oidDataIndex; + + std::vector rootIDs{}; + + /* + * Used exclusively by `TreeBuilder::serialize()` to avoid having + * to allocate a new buffer every time we serialize a `Node`. + */ + std::unique_ptr buffer; + rocksdb::DB *db = nullptr; + std::unordered_set pointers{}; + + uint64_t getDrgnTypeSize(struct drgn_type *type); + uint64_t next(); + bool isContainer(const Variable &variable); + bool isPrimitive(struct drgn_type *type); + bool shouldProcess(uintptr_t pointer); + Node process(NodeID id, Variable variable); + void processContainer(const Variable &variable, Node &node); + template + std::string_view serialize(const T &); + void JSON(NodeID id, std::ofstream &output); +}; diff --git a/src/X86InstDefs.h b/src/X86InstDefs.h new file mode 100644 index 0000000..c3ec2f8 --- /dev/null +++ b/src/X86InstDefs.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ +#pragma once + +static constexpr uint8_t int3Inst = 0xCC; +static constexpr uint8_t nopInst = 0x90; +static constexpr uint8_t movabsrdi0Inst = 0x48; /* movabs %rdi */ +static constexpr uint8_t movabsrdi1Inst = 0xbf; +static constexpr uint8_t movabsrax0Inst = 0x48; /* movabs %rax */ +static constexpr uint8_t movabsrax1Inst = 0xb8; +static constexpr uint8_t callRaxInst0Inst = 0xff; +static constexpr uint8_t callRaxInst1Inst = 0xd0; +static constexpr long syscallInsts = 0x9090909090050fcc; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..4d4a4b0 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,65 @@ +find_package(GTest REQUIRED) +if (TARGET GTest::gmock_main) + # Only modern version of GTest defines the gmock_main target + set(GMOCK_MAIN_LIBS GTest::gmock_main) +else() + # To support older version of the lib, + # We manually locate the lib and its dependencies instead + find_library(GMOCK_MAIN_LIB NAMES libgmock_main.a REQUIRED) + find_library(GMOCK_LIB NAMES libgmock.a REQUIRED) + set(GMOCK_MAIN_LIBS ${GMOCK_MAIN_LIB} ${GMOCK_LIB} GTest::GTest) +endif() +message(STATUS "Using GMockMain: ${GMOCK_MAIN_LIBS}") + +enable_testing() +include(GoogleTest) + +function(cpp_unittest) + cmake_parse_arguments( + PARSE_ARGV 0 + TEST + "" "NAME" "SRCS;DEPS;PROPERTIES" + ) + + add_executable( + ${TEST_NAME} + ${TEST_SRCS} + ) + + target_link_libraries( + ${TEST_NAME} + ${GMOCK_MAIN_LIBS} glog + ${TEST_DEPS} + ) + + target_precompile_headers(${TEST_NAME} REUSE_FROM oicore) + + gtest_discover_tests(${TEST_NAME} PROPERTIES ${TEST_PROPERTIES}) +endfunction() + +# Integration tests +# These tests can't be run in parallel with the other tests. +# There is some sort of conflict triggering the following error: +# dwfl_linux_proc_report: bzip2 decompression failed +# The integration tests are now triggered by the main Makefile. +#add_test( +# NAME integration +# COMMAND make test-integration +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +#) + +# Unit tests +cpp_unittest( + NAME test_parser + SRCS test_parser.cpp + DEPS oid_parser +) + +cpp_unittest( + NAME test_compiler + SRCS test_compiler.cpp + DEPS oicore +) + +include_directories("${PROJECT_SOURCE_DIR}/extern/drgn/build") +add_subdirectory("integration") diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..81303fe --- /dev/null +++ b/test/Makefile @@ -0,0 +1,14 @@ +CXX=clang++ +CXXFLAGS=-g -fdebug-types-section -I../extern/folly -O2 -pthread -no-pie +FILTER ?= + +TARGETS=integration_mttest integration_sleepy integration_packed integration_cycles mapiter mttest1 mttest2 mttest2_inline mttest3 mttest4 tester userDef1 vector inlined_test + +mttest2_inline: CXXFLAGS+=-DMTTEST2_INLINE_DO_STUFF +all: $(TARGETS) + +clean: + rm -f $(TARGETS) + +test-integration: integration_mttest integration_sleepy + sudo python3 integration.py -k '$(FILTER)' diff --git a/test/TARGETS b/test/TARGETS new file mode 100644 index 0000000..ba46f65 --- /dev/null +++ b/test/TARGETS @@ -0,0 +1,31 @@ +load("@fbcode_macros//build_defs:cpp_binary.bzl", "cpp_binary") + +cpp_binary( + name = "mapiter", + srcs = ["mapiter.cpp"], + deps = [ + ], +) + +cpp_binary( + name = "vector", + srcs = ["vector.cpp"], + deps = [ + ], +) + +cpp_binary( + name = "userDef1", + srcs = ["userDef1.cpp"], + deps = [ + ], +) + +cpp_binary( + name = "mttest1", + srcs = ["mttest1.cpp"], + deps = [ + "//common/init:init", + ], + external_deps = [("glibc", None, "pthread")], +) diff --git a/test/ci.oid.toml b/test/ci.oid.toml new file mode 100644 index 0000000..f12d328 --- /dev/null +++ b/test/ci.oid.toml @@ -0,0 +1,58 @@ +[types] +containers = [ + "/home/circleci/project/types/array_type.toml", + "/home/circleci/project/types/string_type.toml", + "/home/circleci/project/types/cxx11_string_type.toml", + "/home/circleci/project/types/folly_iobuf_type.toml", + "/home/circleci/project/types/folly_iobuf_queue_type.toml", + "/home/circleci/project/types/set_type.toml", + "/home/circleci/project/types/unordered_set_type.toml", + "/home/circleci/project/types/seq_type.toml", + "/home/circleci/project/types/list_type.toml", + "/home/circleci/project/types/cxx11_list_type.toml", + "/home/circleci/project/types/deque_list_type.toml", + "/home/circleci/project/types/shrd_ptr_type.toml", + "/home/circleci/project/types/uniq_ptr_type.toml", + "/home/circleci/project/types/std_map_type.toml", + "/home/circleci/project/types/std_unordered_map_type.toml", + "/home/circleci/project/types/pair_type.toml", + "/home/circleci/project/types/stack_container_adapter_type.toml", + "/home/circleci/project/types/queue_container_adapter_type.toml", + "/home/circleci/project/types/priority_queue_container_adapter_type.toml", + "/home/circleci/project/types/ref_wrapper_type.toml", + "/home/circleci/project/types/multi_map_type.toml", + "/home/circleci/project/types/folly_small_heap_vector_map.toml", + "/home/circleci/project/types/folly_optional_type.toml", + "/home/circleci/project/types/optional_type.toml", + "/home/circleci/project/types/try_type.toml", + "/home/circleci/project/types/fb_string_type.toml", + "/home/circleci/project/types/small_vec_type.toml", + "/home/circleci/project/types/f14_fast_map.toml", + "/home/circleci/project/types/f14_node_map.toml", + "/home/circleci/project/types/f14_vector_map.toml", + "/home/circleci/project/types/f14_fast_set.toml", + "/home/circleci/project/types/f14_node_set.toml", + "/home/circleci/project/types/f14_vector_set.toml", + "/home/circleci/project/types/sorted_vec_set_type.toml", + "/home/circleci/project/types/map_seq_type.toml", + "/home/circleci/project/types/boost_bimap_type.toml", + "/home/circleci/project/types/repeated_field_type.toml", + "/home/circleci/project/types/repeated_ptr_field_type.toml", + "/home/circleci/project/types/caffe2_blob_type.toml", + "/home/circleci/project/types/std_variant.toml", + "/home/circleci/project/types/thrift_isset_type.toml", +] + +[headers] +user_paths = [ + "/home/circleci/project/extern/folly", +] +system_paths = [ + "/usr/include/c++/11", + "/usr/include/x86_64-linux-gnu/c++/11", + "/usr/include/c++/11/backward", + "/usr/local/include", + "/usr/lib/llvm-12/lib/clang/12.0.1/include", + "/usr/include/x86_64-linux-gnu", + "/usr/include", +] diff --git a/test/convert_to_junit.sh b/test/convert_to_junit.sh new file mode 100755 index 0000000..58cf60b --- /dev/null +++ b/test/convert_to_junit.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +set -u + +ctest2junit_xsl=$(readlink -f `dirname ${BASH_SOURCE[0]}`)/ctest_to_junit.xsl +tests_dir=$1 + +if [ ! -d $tests_dir ]; +then + echo "ERROR! $tests_dir is not directory!" + exit 1 +fi + +tag=$(head -n 1 $tests_dir/Testing/TAG) +xsltproc --output build/results/ctest/results.xml $ctest2junit_xsl $tests_dir/Testing/$tag/Test.xml + +echo "Test report converted successfully" + diff --git a/test/ctest_to_junit.xsl b/test/ctest_to_junit.xsl new file mode 100644 index 0000000..b95eccd --- /dev/null +++ b/test/ctest_to_junit.xsl @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BuildName: + BuildStamp: + Name: + Generator: + CompilerName: + OSName: + Hostname: + OSRelease: + OSVersion: + OSPlatform: + Is64Bits: + VendorString: + VendorID: + FamilyID: + ModelID: + ProcessorCacheSize: + NumberOfLogicalCPU: + NumberOfPhysicalCPU: + TotalVirtualMemory: + TotalPhysicalMemory: + LogicalProcessorsPerPhysical: + ProcessorClockFrequency: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/inlined_test.cpp b/test/inlined_test.cpp new file mode 100644 index 0000000..185bb67 --- /dev/null +++ b/test/inlined_test.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include + +#define INLINE static inline __attribute__((always_inline)) + +template +INLINE std::vector combine(const std::vector& x, + const std::vector& y) { + std::vector combined; + combined.reserve(x.size() + y.size()); + for (auto& elem : x) + combined.push_back(elem); + for (auto& elem : y) + combined.push_back(elem); + return combined; +} + +template +INLINE std::vector flatten(const std::vector>& vec) { + std::vector flattened; + for (auto& elem : vec) + flattened = combine(elem, flattened); + return flattened; +} + +template +INLINE std::vector flatten_combine(const std::vector>& x, + const std::vector>& y) { + auto x_flat = flatten(x); + auto y_flat = flatten(y); + return combine(x_flat, y_flat); +} + +#define MAX_SIZE 256 + +void fill(std::vector& vec, int n) { + n %= MAX_SIZE; + vec.clear(); + vec.reserve(n); + for (int i = 0; i < n; i++) + vec.push_back(rand()); +} + +void fill_vec(std::vector>& vec, int n) { + n %= MAX_SIZE; + vec.clear(); + vec.reserve(n); + for (int i = 0; i < n; i++) { + vec.emplace_back(); + auto& last = vec.back(); + fill(last, rand()); + } +} + +int main() { + size_t exit_code = 0; + for (int i = 0; i < 100; i++) { + std::vector> x; + std::vector> y; + fill_vec(x, rand()); + fill_vec(y, rand()); + auto result = flatten_combine(x, y); + for (auto value : result) + exit_code += rand() % (value + 1); + sleep(1); + } + return (int)exit_code; +} diff --git a/test/inlined_test_flatten.oid b/test/inlined_test_flatten.oid new file mode 100644 index 0000000..98efc01 --- /dev/null +++ b/test/inlined_test_flatten.oid @@ -0,0 +1 @@ +entry:_ZL7flattenIiESt6vectorIT_SaIS1_EERKS0_IS3_SaIS3_EE:arg0 diff --git a/test/inlined_test_flatten_combine.oid b/test/inlined_test_flatten_combine.oid new file mode 100644 index 0000000..731c548 --- /dev/null +++ b/test/inlined_test_flatten_combine.oid @@ -0,0 +1 @@ +entry:_ZL15flatten_combineIiESt6vectorIT_SaIS1_EERKS0_IS3_SaIS3_EES7_:arg0 diff --git a/test/integration.py b/test/integration.py new file mode 100644 index 0000000..e8850de --- /dev/null +++ b/test/integration.py @@ -0,0 +1,300 @@ +import glob +import json +import os.path +import shutil +import subprocess +import tempfile +import unittest +from contextlib import contextmanager +from enum import Enum + +ExitStatus = Enum( + "ExitStatus", + ( + "SUCCESS USAGE_ERROR FILE_NOT_FOUND_ERROR " + "CONFIG_GENERATION_ERROR SCRIPT_PARSING_ERROR " + "STOP_TARGET_ERROR SEGMENT_REMOVAL_ERROR " + "SEGMENT_INIT_ERROR COMPILATION_ERROR PATCHING_ERROR " + "PROCESSING_TARGET_DATA_ERROR OID_OBJECT_ERROR" + ), + start=0, +) + +OUTPUT_PATH = "oid_out.json" + + +def copy_file(from_path, to_path): + """ + Copies a file from `from_path` to `to_path`, preserving its permissions. + """ + shutil.copy2(from_path, to_path) + shutil.copymode(from_path, to_path) + + +class OIDebuggerTestCase(unittest.TestCase): + def setUp(self): + # Store PWD before moving out of it + self.original_working_directory = os.getcwd() + # Store OI's source directory before moving out of it + self.oid_source_dir = os.path.dirname( + os.path.dirname(os.path.abspath(__file__)) + ) + + # This needs to be a class variable, otherwise it won't be referenced + # by any object alive by the end of this class method's execution and + # and the directory will be automatically removed before executing the + # tests themselves. + self.temp = tempfile.TemporaryDirectory( + dir=os.path.join(self.oid_source_dir, "build/") + ) + os.chdir(self.temp.name) + + self.oid = os.path.join(self.oid_source_dir, "build/oid") + self.oid_conf = os.path.join(self.oid_source_dir, "build/testing.oid.toml") + + self.binary_path = os.path.join( + self.oid_source_dir, "test", "integration_mttest" + ) + self.sleepy_binary_path = os.path.join( + self.oid_source_dir, "test", "integration_sleepy" + ) + + self.custom_generated_code_file = os.path.join( + self.temp.name, "custom_oid_output.cpp" + ) + + self.custom_generated_code_file = f"{self.temp.name}/custom_oid_output.cpp" + self.script_packed = f"{self.temp.name}/integration_packed_arg0.oid" + self.default_script = "integration_entry_doStuff_arg0.oid" + + def tearDown(self): + os.chdir(self.original_working_directory) + self.temp.cleanup() + + @contextmanager + def spawn_oid(self, script_path, test_cmd=None, oid_opt=""): + """ + Spawns a test binary and oid with the specified oid binary path, script + and test command. + + Will take care of cleaning up the process properly. + """ + + if test_cmd is None: + test_cmd = self.binary_path + " 100" + + debuggee_proc = subprocess.Popen( + test_cmd, + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + debuggee_pid = debuggee_proc.pid + + cmd = f"OID_METRICS_TRACE=time {self.oid} --dump-json --config-file {self.oid_conf} --script {script_path} -t60 --pid {debuggee_pid} {oid_opt}" + proc = subprocess.run( + cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + try: + yield proc + finally: + debuggee_proc.terminate() + outs, _errs = debuggee_proc.communicate() + + def script(self, script_name=None): + return os.path.join( + self.oid_source_dir, "test", script_name or self.default_script + ) + + def expectReturncode(self, proc, returncode): + if proc.returncode != returncode.value: + print() + print(proc.stdout.decode("utf-8")) + print(proc.stderr.decode("utf-8")) + + self.assertEqual(proc.returncode, returncode.value) + + def test_help_works(self): + proc = subprocess.run(f"{self.oid} --help", shell=True, stdout=subprocess.PIPE) + self.expectReturncode(proc, ExitStatus.SUCCESS) + self.assertIn(b"usage: ", proc.stdout) + + def test_attach_more_than_once_works(self): + with subprocess.Popen( + f"{self.binary_path} 100", + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) as debuggee_proc: + debuggee_pid = debuggee_proc.pid + + for i in range(2): + proc = subprocess.run( + f"{self.oid} --dump-json --config-file {self.oid_conf} --script {self.script()} -t60 --pid {debuggee_pid}", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.expectReturncode(proc, ExitStatus.SUCCESS) + + debuggee_proc.terminate() + + with open(OUTPUT_PATH, "r") as f: + output = json.loads(f.read()) + self.assertEqual(output[0]["typeName"], "Foo") + self.assertEqual(output[0]["staticSize"], 2176) + self.assertEqual(output[0]["dynamicSize"], 76) + self.assertEqual(len(output[0]["members"]), 25) + + def test_data_segment_size_change(self): + with subprocess.Popen( + f"{self.binary_path} 1000", + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) as debuggee_proc: + debuggee_pid = debuggee_proc.pid + + for data_segment_size in ("1M", "2M", "1M"): + proc = subprocess.run( + f"{self.oid} --dump-json --config-file {self.oid_conf} --script {self.script()} -t60 --pid {debuggee_pid} -x {data_segment_size}", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.expectReturncode(proc, ExitStatus.SUCCESS) + + debuggee_proc.terminate() + + with open(OUTPUT_PATH, "r") as f: + output = json.loads(f.read()) + self.assertEqual(output[0]["typeName"], "Foo") + self.assertEqual(output[0]["staticSize"], 2176) + self.assertEqual(output[0]["dynamicSize"], 76) + self.assertEqual(len(output[0]["members"]), 25) + + def test_custom_generated_file(self): + with subprocess.Popen( + f"{self.binary_path} 100", + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) as debuggee_proc: + debuggee_pid = debuggee_proc.pid + + proc = subprocess.run( + f"{self.oid} --script {self.script()} --config-file {self.oid_conf} -t60 --pid {debuggee_pid}", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.expectReturncode(proc, ExitStatus.SUCCESS) + + copy_file( + "/tmp/tmp_oid_output_2.cpp", + self.custom_generated_code_file, + ) + proc = subprocess.run( + f"{self.oid} --script {self.script()} --config-file {self.oid_conf} --pid {debuggee_pid} -t60 --generated-code-file {self.custom_generated_code_file}", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.expectReturncode(proc, ExitStatus.SUCCESS) + + debuggee_proc.terminate() + + # with open("oid_out.json", "r") as f: + # output = json.loads(f.read()) + # self.assertEqual(output["typeName"], "Foo") + # self.assertEqual(output["staticSize"] + output["dynamicSize"], 2062) + # self.assertEqual(len(output["members"]), 9) + + # debuggee_proc.terminate() + # outs, _errs = debuggee_proc.communicate() + + def test_symbol_not_found_in_binary_fails(self): + with self.spawn_oid(self.script(), test_cmd="/bin/sleep 100") as proc: + self.expectReturncode(proc, ExitStatus.COMPILATION_ERROR) + self.assertIn( + b"Failed to create FuncDesc for", + proc.stderr, + ) + + def test_non_existant_script_fails(self): + with self.spawn_oid("not_there.oid") as proc: + self.expectReturncode(proc, ExitStatus.FILE_NOT_FOUND_ERROR) + + def test_no_args_shows_help(self): + proc = subprocess.run( + self.oid, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.expectReturncode(proc, ExitStatus.USAGE_ERROR) + self.assertIn(b"usage: ", proc.stdout) + + def test_metrics_data_is_generated(self): + with self.spawn_oid(self.script()) as proc: + self.expectReturncode(proc, ExitStatus.SUCCESS) + + # Just checking that the file exists and it's valid JSON + with open("oid_metrics.json", "r") as f: + json.loads(f.read()) + + # Ensure removeTrap properly repatch small functions + def test_probe_return_arg0_small_fun(self): + # Check function incN is smaller than the POKETEXT window (8 bytes) + readIncNSize = subprocess.run( + f"readelf -s {self.binary_path} | grep _ZN3Foo4incNEi | awk '{{print $3}}'", + capture_output=True, + shell=True, + check=True, + ) + incNSize = int(readIncNSize.stdout.decode("ascii")) + self.assertLessEqual(incNSize, 8) + + with self.spawn_oid(self.script("integration_return_incN_arg0.oid")) as proc: + self.expectReturncode(proc, ExitStatus.SUCCESS) + + with open(OUTPUT_PATH, "r") as f: + output = json.loads(f.read()) + self.assertEqual(output[0]["typeName"], "int") + self.assertEqual(output[0]["staticSize"] + output[0]["dynamicSize"], 4) + self.assertNotIn("members", output[0]) + + def test_error_function_no_this(self): + with self.spawn_oid(self.script("integration_entry_doStuff_this.oid")) as proc: + self.expectReturncode(proc, ExitStatus.COMPILATION_ERROR) + self.assertIn( + b"has no 'this' parameter", + proc.stderr, + ) + + def test_error_method_no_arg0(self): + with self.spawn_oid(self.script("integration_entry_inc_arg0.oid")) as proc: + self.expectReturncode(proc, ExitStatus.COMPILATION_ERROR) + self.assertIn( + b"Argument index 0 too large. Args count: 0", + proc.stderr, + ) + + def test_error_timeout(self): + with self.spawn_oid( + self.script(), + test_cmd=f"{self.sleepy_binary_path} 100", + oid_opt="-d2 --timeout=1", + ) as proc: + self.expectReturncode(proc, ExitStatus.SUCCESS) + self.assertIn(b"Received SIGNAL 14", proc.stderr) + self.assertIn(b"processTrap: Error in waitpid", proc.stderr) + self.assertIn(b"Interrupted system call", proc.stderr) + + +if __name__ == "__main__": + print("[debug] Running OI's integration tests") + unittest.main(verbosity=2) diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt new file mode 100644 index 0000000..7b55b68 --- /dev/null +++ b/test/integration/CMakeLists.txt @@ -0,0 +1,130 @@ +# Add new test definition files to this list: +set(INTEGRATION_TEST_CONFIGS + anonymous.toml + container_enums.toml + cycles.toml + multi_arg.toml + namespaces.toml + packed.toml + padding.toml + pointers.toml + pointers_function.toml + pointers_incomplete.toml + primitives.toml + references.toml + simple_multiple_multilevel_inheritance.toml + simple_struct.toml + std_array.toml + std_deque_del_allocator.toml + std_list_del_allocator.toml + std_deque.toml + std_map_custom_comparator.toml + std_optional.toml + std_pair.toml + std_queue.toml + std_reference_wrapper.toml + std_priority_queue.toml + std_set_custom_comparator.toml + std_smart_ptr.toml + std_stack.toml + std_string.toml + std_unordered_map.toml + std_unordered_map_custom_operator.toml + std_unordered_set_custom_operator.toml + std_variant.toml + std_vector.toml + std_vector_del_allocator.toml + typedefed_parent.toml +) + +find_package(Thrift) +if (${THRIFT_FOUND}) + # Add test definition files requiring the Thrift compiler to this list: + set(THRIFT_TEST_CONFIGS + thrift_isset.toml + thrift_isset_missing.toml + thrift_namespaces.toml + ) + list(APPEND INTEGRATION_TEST_CONFIGS ${THRIFT_TEST_CONFIGS}) +endif() + +list(TRANSFORM INTEGRATION_TEST_CONFIGS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") + +# disable position independent executables that oid can't yet handle +# todo: update to more modern cmake syntax +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -no-pie") + +set(INTEGRATION_TEST_TARGET_SRC integration_test_target.cpp) +set(INTEGRATION_TEST_RUNNER_SRC integration_test_runner.cpp) + +find_program(PYTHON_CMD NAMES python3.6 python3) + +set(THRIFT_TESTS ${THRIFT_TEST_CONFIGS}) +list(TRANSFORM THRIFT_TESTS REPLACE ".toml$" "") + +set(INTEGRATION_TEST_THRIFT_SRCS ${THRIFT_TESTS}) +list(TRANSFORM INTEGRATION_TEST_THRIFT_SRCS APPEND ".thrift") + +add_custom_command( + OUTPUT + ${INTEGRATION_TEST_TARGET_SRC} + ${INTEGRATION_TEST_RUNNER_SRC} + ${INTEGRATION_TEST_THRIFT_SRCS} + COMMAND ${PYTHON_CMD} + ${CMAKE_CURRENT_SOURCE_DIR}/gen_tests.py + ${INTEGRATION_TEST_TARGET_SRC} + ${INTEGRATION_TEST_RUNNER_SRC} + ${INTEGRATION_TEST_CONFIGS} + MAIN_DEPENDENCY gen_tests.py + DEPENDS ${INTEGRATION_TEST_CONFIGS}) + +add_executable(integration_test_target ${INTEGRATION_TEST_TARGET_SRC}) +target_compile_options(integration_test_target PRIVATE -O1) +target_link_libraries(integration_test_target PRIVATE oil Boost::headers ${Boost_LIBRARIES}) + +add_executable(integration_test_runner ${INTEGRATION_TEST_RUNNER_SRC} runner_common.cpp) +target_include_directories(integration_test_runner PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +# GMOCK_MAIN_LIBS is set in test/CMakeLists.txt +target_link_libraries(integration_test_runner PRIVATE + ${GMOCK_MAIN_LIBS} + Boost::headers + ${Boost_LIBRARIES}) +target_compile_definitions(integration_test_runner PRIVATE + TARGET_EXE_PATH="${CMAKE_CURRENT_BINARY_DIR}/integration_test_target" + OID_EXE_PATH="$" + CONFIG_FILE_PATH="${CMAKE_BINARY_DIR}/testing.oid.toml") + +if (${THRIFT_FOUND}) + foreach(THRIFT_TEST IN LISTS THRIFT_TESTS) + set(THRIFT_SRC "${THRIFT_TEST}.thrift") + set(THRIFT_TYPES_H "thrift/annotation/gen-cpp2/${THRIFT_TEST}_types.h") + set(THRIFT_DATA_CPP "thrift/annotation/gen-cpp2/${THRIFT_TEST}_data.cpp") + add_custom_command( + OUTPUT + ${THRIFT_TYPES_H} + ${THRIFT_DATA_CPP} + COMMAND + ${THRIFT_COMPILER} + -r + --gen mstch_cpp2 + -o thrift/annotation/ + -I ${THRIFT_INCLUDE_DIRS} + ${THRIFT_SRC} + MAIN_DEPENDENCY ${THRIFT_SRC}) + + add_custom_target(integration_test_thrift_sources_${THRIFT_TEST} DEPENDS ${THRIFT_TYPES_H}) + add_dependencies(integration_test_target integration_test_thrift_sources_${THRIFT_TEST}) + target_sources(integration_test_target PRIVATE ${THRIFT_DATA_CPP}) + endforeach() + + target_include_directories(integration_test_target PRIVATE + ${THRIFT_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(integration_test_target PRIVATE glog::glog) +endif() + +if (DEFINED ENV{CI}) + gtest_discover_tests(integration_test_runner EXTRA_ARGS "--verbose" "--preserve-on-failure") +else() + gtest_discover_tests(integration_test_runner) +endif() diff --git a/test/integration/README.md b/test/integration/README.md new file mode 100644 index 0000000..88a0785 --- /dev/null +++ b/test/integration/README.md @@ -0,0 +1,273 @@ +# Integration Tests + +This directory contains test definition files for OI's integration tests. + +## Running tests + +There are a number of ways to run these integration tests. + +1. Run the `integration_test_runner` executable directly. This provides some +additional options to aid debugging: + - `--verbose` Verbose output + - `--preserve` Do not clean up files generated by OID after tests are finished + - `--force` Run tests that have been marked as "skipped" + +1. Run a number of the integration tests in parallel: +```ctest --test-dir build/test/integration -j$(nproc) [--tests-regex ]``` + +1. Run along with all tests with either of: +``` +make test-devel +make test-static +``` + +## Adding tests + +1. Create a new test definition file in this directory and populate it as needed. See [Test Definition Format](#test-definition-format) for details. +1. Add your new definition file to the `INTEGRATION_TEST_CONFIGS` list in [`CMakeLists.txt`](CMakeLists.txt) + +## Test Definition Format + +Test definitions are stored in the [TOML](https://toml.io/) file format. + +Example: +```toml +includes = ["vector", "unordered_map"] +definitions = ''' + struct Foo { + std::vector v; + }; + using Bar = std::unordered_map; +''' +[cases] + [cases.my_first_test_case] + param_types = ["const Foo&", "const Bar&"] + setup = ''' + Foo foo; + foo.v = {4,5,6}; + Bar bar; + bar[2] = 3; + return {foo, bar}; + ''' + expect_json = '{"staticSize":4,"dynamicSize":32}' + [cases.another_test_case] + param_types = ["int"] + setup = 'return 123;' +``` + +### Details + +- `includes` + + Header files required for this test. + + Example: + ``` + includes = ["vector", "unordered_map"] + ``` + +- `definitions` + + C++ type definitions required for a test can be defined here. + + Anything defined in this section will be automatically wrapped in a namespace + and will be private to this test. + + Example: + ``` + definitions = ''' + struct Foo { + std::vector v; + }; + using Bar = std::unordered_map; + ''' + ``` + +- `thrift_definitions` + + Thrift type definitions can be specified here. These will be passed to the + Thrift compiler which will generate C++ code from them. + + **CAUTION**: Generated Thrift types are not wrapped in a namespace, so type + names must be globally unique between all tests. + + Example: + ``` + thrift_definitions = ''' + struct MyThriftStruct { + 1: optional i32 a; + 2: optional i32 b; + } + ''' + ``` + +- `raw_definitions` + + This section allows specifying of arbitrary C++ code which will be directly + copied into the target program's source without being wrapped in a namespace. + + It should not be used for most tests. The purpose is to allow defining code + required for a specific test to compile, avoiding the need to add new + dependencies to the build system for one-off tests. + +- `cases` **Required** + + A list of individual test cases, each with their own setup, OI probe + definition and expected results, but sharing any type definitions created in + this test file. + + Test cases should be grouped into related areas and put into shared test files. + + - `param_types` **Required** + + Paramter types of the function to probe. + + oid does not have complete support for probing pass-by-value parameters, so + it is recommended to define all parameters as reference or pointer types. + + Example: + ``` + param_types = ["const std::vector&", "const Foo&"] + ``` + + - `arg_types` + + Types of the arguments being passed to the probed function. Defaults to + `param_types` with const, volatile and references removed. + + It is only necessary to specify `arg_types` when they will differ from the + parameter types expected by the probed function. This can be useful for + testing inheritance. + + Example: + ``` + param_types = ["BaseClass *"] + arg_types = ["DerivedClass"] + ``` + + - `setup` **Required** + + A snippit of C++ code which creates and returns values to be passed to the + function being probed as a part of this test case. The returned value should + be a tuple of `param_types`, although the curly brakcets/braces can be + omitted in most cases when there is only a single value in the tuple. + + Example: + ``` + setup = ''' + std::vector ret = {1,2,3}; + return {ret, Foo(1)}; + ''' + ``` + + - `type` + + OI probe type. Defaults to `entry`. + + Example: + ``` + type = "return" + ``` + + - `args` + + Comma separated list of arguments to introspect. Defaults to `arg0`. + + Example: + ``` + args = "arg0,arg1" + ``` + + - `cli_options` + + Additional command line arguments passed to oid. + + Example: + ``` + cli_options = ["--chase-raw-pointers"] + ``` + + - `oid_skip`, `oil_skip` + + Skip running this test. Defaults to false. + + Example: + ``` + oid_skip = true + ``` + + - `expect_oid_exit_code`, `expect_oil_exit_code` + + Exit code expected from OI. Defaults to 0. + + Example: + ``` + expect_oid_exit_code = 6 + ``` + + - `expect_json` + + JSON expected to match results from OI. + + Only keys which appear in these expected results are used for comparison. + This means that irrelevant or non-reproducable keys can be omitted and they + will be ignored. Missing keys in the actual results will still cause test + failures. + + Example: + ``` + expect_json = '{"staticSize":4,"dynamicSize":0}' + ``` + + To ensure that a given key does not appear in the results, the special + **NOT** key can be used, with the value set to the undesired key's name. + + This example checks that the JSON result does not contain the key "members": + ``` + expect_json = '{"NOT":"members"}' + ``` + + The **NOT** key can also be used to check that a given key's value is not + equal to some expected value. + + This example checks that the result has a key named `pointer`, but that its + value is not equal to 0: + ``` + expect_json = '{"NOT":{"pointer":0}}' + ``` + + - `expect_stdout` + + Regex expected to match OI's stdout. + + Example: + ``` + expect_stdout = ".*SUCCESS.*" + ``` + + - `expect_stderr` + + Regex expected to match OI's stderr. + + Example: + ``` + expect_stderr = ".*Successfully detached from pid.*" + ``` + + - `expect_not_stdout` + + Regex expected to not match OI's stdout. + + Example: + ``` + expect_not_stdout = "ABC" + ``` + + - `expect_not_stderr` + + Regex expected to not match OI's stderr. + + Example: + ``` + expect_not_stderr = ".*ERROR.*" + ``` diff --git a/test/integration/anonymous.toml b/test/integration/anonymous.toml new file mode 100644 index 0000000..03aa2af --- /dev/null +++ b/test/integration/anonymous.toml @@ -0,0 +1,312 @@ +definitions = ''' + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wnested-anon-types" + + struct Node { + int a, b, c; + }; + + struct DynNode { + std::vector nodes; + }; + + typedef struct { + struct Node *node; + } AnonStruct; + + struct AnonStructContainer { + struct { struct Node *node; } anon; + }; + + struct AnonStructPtrContainer { + struct { struct Node *node; } *anon; + }; + + struct AnonTypedefContainer { + AnonStruct anon; + }; + + struct AnonUnionContainer { + union {char c; short int d;}; + union {int a; double b;}; + int e; + }; + + struct NestedAnonContainer { + struct { + union { + char c; + struct { + int x; + double y; + } d; + }; + struct { + int a, b, c; + union { + union { char x; int y; }; + long z; + }; + AnonStruct as; + } v; + } m; + + union { + union { int x1; char y1; }; + union { double z1; long w1; }; + struct { + long a, b, c; + } s1; + }; + + union { + union { int x2; char y2; }; + union { double z2; long w2; }; + struct { + char a, b, c; + } s2; + }; + }; + + /* This test is disable due to GCC not supporting it + struct AnonArrayContainer { + struct { + float *x; + DynNode ns[4]; + }; + }; + */ + + #pragma clang diagnostic pop +''' + +[cases] + [cases.regular_struct] + param_types = ["const Node&"] + setup = "return Node{1, 2, 3};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":12, + "dynamicSize":0, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0}, + {"name":"b", "staticSize":4, "dynamicSize":0}, + {"name":"c", "staticSize":4, "dynamicSize":0} + ]}]''' + + [cases.anon_struct] + oil_skip = "oil can't chase raw pointers safely" + param_types = ["const AnonStructContainer&"] + setup = ''' + return AnonStructContainer{ + .anon = { + .node = new Node{1, 2, 3} + } + }; + ''' + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "anon", + "typeName": "__anon_struct_0", + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "node", + "typeName": "struct Node *", + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "typeName": "Node", + "staticSize": 12, + "dynamicSize": 0, + "members": [ + { "name": "a", "staticSize": 4, "dynamicSize": 0 }, + { "name": "b", "staticSize": 4, "dynamicSize": 0 }, + { "name": "c", "staticSize": 4, "dynamicSize": 0 } + ] + }] + }] + }] + }]''' + + [cases.anon_struct_ptr] + skip = "We don't support pointer to anon-structs yet" + param_types = ["const AnonStructPtrContainer&"] + setup = ''' + return AnonStructPtrContainer{ + .anon = (decltype(AnonStructPtrContainer::anon))new (Node*){ + new Node{1, 2, 3} + } + };''' + cli_options = ["--chase-raw-pointers"] + + [cases.anon_typedef] + oil_skip = "oil can't chase raw pointers safely" + param_types = ["const AnonTypedefContainer&"] + setup = ''' + return AnonTypedefContainer{ + .anon = { + .node = new Node{1, 2, 3} + } + };''' + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "anon", + "typeName": "AnonStruct", + "isTypedef": true, + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "", + "typeName": "__anon_struct_0", + "isTypedef": false, + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "node", + "typeName": "struct Node *", + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "typeName": "Node", + "staticSize": 12, + "members": [ + { "name": "a", "staticSize": 4, "dynamicSize": 0 }, + { "name": "b", "staticSize": 4, "dynamicSize": 0 }, + { "name": "c", "staticSize": 4, "dynamicSize": 0 } + ] + }] + }] + }] + }] + }]''' + + [cases.anon_union] + param_types = ["const AnonUnionContainer&"] + setup = 'return AnonUnionContainer{ .a = 3 };' + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 24, + "dynamicSize": 0, + "members": [ + {"name":"__anon_member_0", "staticSize":2, "dynamicSize":0}, + {"name":"__anon_member_1", "staticSize":8, "dynamicSize":0}, + {"name":"e", "staticSize":4, "dynamicSize":0, "typeName":"int"} + ] + }]''' + + [cases.nested_anon_struct] + oil_skip = "oil can't chase raw pointers safely" + param_types = ["const NestedAnonContainer&"] + setup = 'return NestedAnonContainer{.m = { .v = {.as = {new Node{1, 2, 3}}}}};' + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 80, + "dynamicSize": 12, + "members": [{ + "name": "m", + "staticSize": 48, + "dynamicSize": 12, + "members": [ + { "staticSize": 16, "dynamicSize": 0 }, + { "name": "v", + "staticSize": 32, + "dynamicSize": 12, + "paddingSavingsSize": 4, + "members": [ + { "name": "a", "staticSize": 4, "dynamicSize": 0 }, + { "name": "b", "staticSize": 4, "dynamicSize": 0 }, + { "name": "c", "staticSize": 4, "dynamicSize": 0 }, + { "staticSize": 8, "dynamicSize": 0 }, + { "name": "as", + "typeName": "AnonStruct", + "isTypedef": true, + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "isTypedef": false, + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "name": "node", + "staticSize": 8, + "dynamicSize": 12, + "members": [{ + "staticSize": 12, + "dynamicSize": 0, + "members": [ + { "name": "a", "staticSize": 4, "dynamicSize": 0 }, + { "name": "b", "staticSize": 4, "dynamicSize": 0 }, + { "name": "c", "staticSize": 4, "dynamicSize": 0 } + ] + }] + }] + }] + }] + }] + }, { + "staticSize": 24, + "dynamicSize": 0 + }, { + "staticSize": 8, + "dynamicSize": 0 + }] + }]''' + + # This test is disabled due to GCC not supporting it + # [cases.anon_array] + # param_types = ["const AnonArrayContainer&"] + # setup = ''' + # auto result = AnonArrayContainer{ + # .x = new float{123.456}, + # .ns = { + # DynNode{ .nodes = { Node{1, 2, 3}, Node{4, 5, 6}, Node{7, 8, 9} } }, + # DynNode{}, + # DynNode{ .nodes = std::vector(1, Node{0, 0, 0}) }, + # DynNode{ .nodes = std::vector(42, Node{1, 1, 1}) }, + # }, + # }; + # result.ns[3].nodes.resize(22); + # return result; + # ''' + # cli_options = ["--chase-raw-pointers"] + # expect_json = '''[{ + # "staticSize": 104, + # "dynamicSize": 556, + # "members": [{ + # "staticSize": 104, + # "dynamicSize": 556, + # "members": [{ + # "name": "x", + # "staticSize": 8, + # "dynamicSize": 4, + # "members": [{ "staticSize": 4, "dynamicSize": 0 }] + # }, { + # "name": "ns", + # "staticSize": 96, + # "dynamicSize": 552, + # "length": 4, + # "capacity": 4, + # "elementStaticSize": 24, + # "members": [{ + # "dynamicSize": 36, + # "members": [{ "length": 3, "capacity": 3, "elementStaticSize": 12 }] + # }, { + # "dynamicSize": 0, + # "members": [{ "length": 0, "capacity": 0, "elementStaticSize": 12 }] + # }, { + # "dynamicSize": 12, + # "members": [{ "length": 1, "capacity": 1, "elementStaticSize": 12 }] + # }, { + # "dynamicSize": 504, + # "members": [{ "length": 22, "capacity": 42, "elementStaticSize": 12 }] + # }] + # }] + # }] + # }]''' diff --git a/test/integration/container_enums.toml b/test/integration/container_enums.toml new file mode 100644 index 0000000..54953d8 --- /dev/null +++ b/test/integration/container_enums.toml @@ -0,0 +1,32 @@ +includes = ["vector"] +definitions = ''' + namespace MyNS { + enum class ScopedEnum { + Zero = 0, + One = 1, + Two = 2, + }; + + enum UNSCOPED_ENUM { + ZERO = 0, + ONE = 1, + TWO = 2, + }; + } // MyNS +''' + +[cases] + [cases.scoped_enum_type] + param_types = ["const std::vector&"] + setup = "return {};" + [cases.scoped_enum_val] + param_types = ["const std::array(MyNS::ScopedEnum::Two)>&"] + setup = "return {};" + expect_json = '[{"staticSize":8, "dynamicSize":0, "length":2, "capacity":2, "elementStaticSize":4}]' + [cases.unscoped_enum_type] + param_types = ["const std::vector&"] + setup = "return {};" + [cases.unscoped_enum_val] + param_types = ["const std::array&"] + setup = "return {};" + expect_json = '[{"staticSize":4, "dynamicSize":0, "length":1, "capacity":1, "elementStaticSize":4}]' diff --git a/test/integration/cycles.toml b/test/integration/cycles.toml new file mode 100644 index 0000000..95c5086 --- /dev/null +++ b/test/integration/cycles.toml @@ -0,0 +1,134 @@ +includes = ["memory"] +definitions = ''' + struct RawNode { + uint64_t value; + struct RawNode* next; + }; + + struct UniqueNode { + uint64_t value; + std::unique_ptr next; + }; + + struct SharedNode { + uint64_t value; + std::shared_ptr next; + }; +''' +[cases] + [cases.raw_ptr] + oil_skip = "oil can't chase pointers safely" + param_types = ["RawNode*"] + setup = ''' + RawNode *first = new RawNode{1, nullptr}; + RawNode *second = new RawNode{2, nullptr}; + RawNode *third = new RawNode{3, nullptr}; + first->next = second; + second->next = third; + third->next = first; + return first; + ''' + cli_options = ["--chase-raw-pointers"] + expect_json = ''' + [{ + "typeName": "RawNode", + "isTypedef": false, + "staticSize": 16, + "dynamicSize": 32, + "members": [ + { + "name": "value", + "typePath": "value", + "typeName": "uint64_t", + "staticSize": 8, + "dynamicSize": 0 + }, + { + "name": "next", + "typePath": "next", + "typeName": "struct RawNode *", + "isTypedef": false, + "staticSize": 8, + "dynamicSize": 32, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "RawNode", + "isTypedef": false, + "staticSize": 16, + "dynamicSize": 16, + "members": [ + { + "name": "value", + "typePath": "value", + "typeName": "uint64_t", + "staticSize": 8, + "dynamicSize": 0 + }, + { + "name": "next", + "typePath": "next", + "typeName": "struct RawNode *", + "isTypedef": false, + "staticSize": 8, + "dynamicSize": 16, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "RawNode", + "isTypedef": false, + "staticSize": 16, + "dynamicSize": 0, + "members": [ + { + "name": "value", + "typePath": "value", + "typeName": "uint64_t", + "staticSize": 8, + "dynamicSize": 0 + }, + { + "name": "next", + "typePath": "next", + "typeName": "struct RawNode *", + "isTypedef": false, + "staticSize": 8, + "dynamicSize": 0 + } + ] + } + ] + } + ] + } + ] + } + ] + }] + ''' + + [cases.unique_ptr] + param_types = ["UniqueNode*"] + setup = ''' + auto first = std::make_unique(); + auto firstPtr = first.get(); + first->next = std::make_unique(); + first->next->next = std::make_unique(); + first->next->next->next = std::move(first); + return firstPtr; + ''' + # TODO check JSON + + [cases.shared_ptr] + param_types = ["SharedNode*"] + setup = ''' + auto first = std::make_shared(); + auto firstPtr = first.get(); + first->next = std::make_shared(); + first->next->next = std::make_shared(); + first->next->next->next = first; + return firstPtr; + ''' + # TODO check JSON diff --git a/test/integration/gen_tests.py b/test/integration/gen_tests.py new file mode 100644 index 0000000..a055049 --- /dev/null +++ b/test/integration/gen_tests.py @@ -0,0 +1,452 @@ +import json +import os +import pathlib +import sys + +import toml + + +def is_thrift_test(config): + return "thrift_definitions" in config + + +def get_case_name(test_suite, test_case): + return f"{test_suite}_{test_case}" + + +def get_target_oid_func_name(test_suite, test_case): + case_name = get_case_name(test_suite, test_case) + return f"oid_test_case_{case_name}" + + +def get_target_oil_func_name(test_suite, test_case): + case_name = get_case_name(test_suite, test_case) + return f"oil_test_case_{case_name}" + + +def get_namespace(test_suite): + return f"ns_{test_suite}" + + +def add_headers(f, custom_headers, thrift_headers): + f.write( + """ +#include +#include +#include +#include +#include +#include + +#include + +""" + ) + for header in custom_headers: + f.write(f"#include <{header}>\n") + + for header in thrift_headers: + f.write(f'#include "{header}"\n') + + +def add_test_setup(f, config): + ns = get_namespace(config["suite"]) + # fmt: off + f.write( + f"\n" + f'{config.get("raw_definitions", "")}\n' + f"namespace {ns} {{\n" + f'{config.get("definitions", "")}\n' + ) + + # fmt: on + + def define_traceable_func(name, params, body): + return ( + f"\n" + f' extern "C" {{\n' + f" void __attribute__((noinline)) {name}({params}) {{\n" + f"{body}" + f" }}\n" + f" }}\n" + ) + + cases = config["cases"] + for case_name, case in cases.items(): + # generate getter for an object of this type + param_types = ", ".join( + f"std::remove_cvref_t<{param}>" for param in case["param_types"] + ) + if "arg_types" in case: + arg_types = ", ".join(case["arg_types"]) + else: + arg_types = param_types + + f.write( + f"\n" + f" std::tuple<{arg_types}> get_{case_name}() {{\n" + f'{case["setup"]}\n' + f" }}\n" + ) + + # generate oid and oil targets + params_str = ", ".join( + f"{param} a{i}" for i, param in enumerate(case["param_types"]) + ) + + oid_func_body = "".join( + f" std::cout << (uintptr_t)(&a{i}) << std::endl;\n" + for i in range(len(case["param_types"])) + ) + oid_func_body += " std::cout << BOOST_CURRENT_FUNCTION << std::endl;\n" + + f.write( + define_traceable_func( + get_target_oid_func_name(config["suite"], case_name), + params_str, + oid_func_body, + ) + ) + + oil_func_body = ( + f"\n" + f"ObjectIntrospection::options opts{{\n" + f' .configFilePath = std::getenv("CONFIG_FILE_PATH"),\n' + f" .debugLevel = 3,\n" + f' .sourceFileDumpPath = "oil_jit_code.cpp",\n' + f" .forceJIT = true,\n" + f"}};" + ) + + oil_func_body += ' std::cout << "{\\"results\\": [" << std::endl;\n' + oil_func_body += ' std::cout << "," << std::endl;\n'.join( + f" size_t size{i} = 0;\n" + f" auto ret{i} = ObjectIntrospection::getObjectSize(&a{i}, &size{i}, opts);\n" + f' std::cout << "{{\\"ret\\": " << ret{i} << ", \\"size\\": " << size{i} << "}}" << std::endl;\n' + for i in range(len(case["param_types"])) + ) + oil_func_body += ' std::cout << "]}" << std::endl;\n' + + f.write( + define_traceable_func( + get_target_oil_func_name(config["suite"], case_name), + params_str, + oil_func_body, + ) + ) + + f.write(f"}} // namespace {ns}\n") + + +def add_common_code(f): + f.write( + """ +int main(int argc, char *argv[]) { + if (argc < 3 || argc > 4) { + std::cerr << "usage: " << argv[0] << " oid/oil CASE [ITER]" << std::endl; + return -1; + } + + std::string mode = argv[1]; + std::string test_case = argv[2]; + + int iterations = 1000; + if (argc == 4) { + std::istringstream iss(argv[3]); + iss >> iterations; + if (iss.fail()) + iterations = 1000; + } + +""" + ) + + +def add_dispatch_code(f, config): + ns = get_namespace(config["suite"]) + for case_name in config["cases"]: + case_str = get_case_name(config["suite"], case_name) + oil_func_name = get_target_oil_func_name(config["suite"], case_name) + oid_func_name = get_target_oid_func_name(config["suite"], case_name) + f.write( + f' if (test_case == "{case_str}") {{\n' + f" auto val = {ns}::get_{case_name}();\n" + f" for (int i=0; i sizes;\n" + f' for (const auto& each : result_json.get_child("results")) {{\n' + f" const auto& result = each.second;\n" + f' int oilResult = result.get("ret");\n' + f' size_t oilSize = result.get("size");\n' + f" ASSERT_EQ(oilResult, 0);\n" + f" sizes.push_back(oilSize);\n" + f" }}" + ) + + if "expect_json" in case: + try: + json.loads(case["expect_json"]) + except json.decoder.JSONDecodeError as error: + print( + f"\x1b[31m`expect_json` value for test case {config['suite']}.{case_name} was invalid JSON: {error}\x1b[0m", + file=sys.stderr, + ) + sys.exit(1) + + f.write( + f"\n" + f" std::stringstream expected_json_ss;\n" + f' expected_json_ss << R"--({case["expect_json"]})--";\n' + f" bpt::ptree expected_json;\n" + f" bpt::read_json(expected_json_ss, expected_json);\n" + f" auto sizes_it = sizes.begin();\n" + f" for (auto it = expected_json.begin(); it != expected_json.end(); ++it, ++sizes_it) {{\n" + f" auto node = it->second;\n" + f' size_t expected_size = node.get("staticSize");\n' + f' expected_size += node.get("dynamicSize");\n' + f" EXPECT_EQ(*sizes_it, expected_size);\n" + f" }}\n" + ) + + f.write(f"}}\n") + + +def generate_skip(case, specific): + possibly_skip = "" + skip_reason = case.get("skip", False) + specific_skip_reason = case.get(f"{specific}_skip", False) + if specific_skip_reason or skip_reason: + possibly_skip += " if (!run_skipped_tests) {\n" + possibly_skip += " GTEST_SKIP()" + if type(specific_skip_reason) == str: + possibly_skip += f' << "{specific_skip_reason}"' + elif type(skip_reason) == str: + possibly_skip += f' << "{skip_reason}"' + possibly_skip += ";\n" + possibly_skip += " }\n" + + return possibly_skip + + +def gen_runner(output_runner_name, test_configs): + with open(output_runner_name, "w") as f: + f.write( + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + '#include "runner_common.h"\n' + "\n" + "namespace ba = boost::asio;\n" + "namespace bpt = boost::property_tree;\n" + "\n" + "using ::testing::MatchesRegex;\n" + "\n" + "extern bool run_skipped_tests;\n" + ) + for config in test_configs: + add_tests(f, config) + + +def gen_thrift(test_configs): + for config in test_configs: + if not is_thrift_test(config): + continue + output_thrift_name = f"{config['suite']}.thrift" + with open(output_thrift_name, "w") as f: + f.write(config["thrift_definitions"]) + print(f"Thrift out: {output_thrift_name}") + + +def main(): + if len(sys.argv) < 4: + print("Usage: gen_tests.py OUTPUT_TARGET OUTPUT_RUNNER INPUT1 [INPUT2 ...]") + exit(1) + + output_target = sys.argv[1] + output_runner = sys.argv[2] + inputs = sys.argv[3:] + + print(f"Output target: {output_target}") + print(f"Output runner: {output_runner}") + print(f"Input files: {inputs}") + + test_configs = [] + test_suites = set() + while len(inputs) > 0: + test_path = inputs.pop() + if test_path.endswith(".toml"): + test_suite = pathlib.Path(test_path).stem + if test_suite in test_suites: + raise Exception(f"Test suite {test_suite} is defined multiple times") + test_suites.add(test_suite) + config = toml.load(test_path) + config["suite"] = test_suite + test_configs += [config] + elif os.path.isdir(test_path): + for root, dirs, files in os.walk(test_path): + for name in files: + if name.endswith(".toml"): + path = os.path.join(root, name) + print("Found definition file at {path}") + inputs.append(path) + else: + raise Exception( + "Test definition inputs must have the '.toml' extension or be a directory" + ) + + gen_target(output_target, test_configs) + gen_runner(output_runner, test_configs) + gen_thrift(test_configs) + + +if __name__ == "__main__": + main() diff --git a/test/integration/ignored.toml b/test/integration/ignored.toml new file mode 100644 index 0000000..c5910e5 --- /dev/null +++ b/test/integration/ignored.toml @@ -0,0 +1,38 @@ +definitions = ''' + struct Foo { + int a, b, c; + }; + + struct Bar { + std::string a, b, c; + }; +''' + +[cases] + [cases.a] + oil_skip = "OIL doesn't support the 'codegen.ignore' config yet" + param_types = ["const Bar&"] + setup = """ + return Bar{ + "The first member of the struct Bar", + "The second member of the struct Bar", + "The 3rd member of the struct Bar" + }; + """ + config = """ + [[codegen.ignore]] + type = "Foo" + members = ["a"] + + [[codegen.ignore]] + type = "Bar" + members = ["b"] + """ + expect_json = '''[{ + "staticSize":96, + "dynamicSize":66, + "members":[ + {"name":"a", "staticSize":32, "dynamicSize":34}, + {"name":"b", "staticSize":32, "dynamicSize":0}, + {"name":"c", "staticSize":32, "dynamicSize":32} + ]}]''' diff --git a/test/integration/multi_arg.toml b/test/integration/multi_arg.toml new file mode 100644 index 0000000..b6183d1 --- /dev/null +++ b/test/integration/multi_arg.toml @@ -0,0 +1,49 @@ +definitions = ''' + struct NodeA { + int x, y, z; + }; + + // Structure that mimic the inside of a std::string + // So we can create bogus std::string and test TreeBuilder failures + struct StringInner { + uintptr_t buffer, size, capacity, extra; + }; +''' + +[cases] + [cases.a] + param_types = ["int", "double"] + args = "arg0,arg1" + setup = "return {1,2.0};" + expect_json = '[{"staticSize":4, "dynamicSize":0},{"staticSize":8, "dynamicSize":0}]' + # TODO separate sizes for each argument? + + # Test that TreeBuilder failing to run on the first arg doesn't impact the second arg + [cases.tb_fail_first_arg] + oil_skip = "oil doesn't handle invalid strings" + param_types = ["const std::string&", "const NodeA&"] + args = "arg0,arg1" + setup = """ + // Create a string with an invalid size/capacity to trip TreeBuilder + StringInner strIn{0, (uintptr_t)-1, (uintptr_t)-1, 0}; + std::string *str = (std::string*)&strIn; + return { std::move(*str), NodeA{4, 5, 6} }; + """ + expect_json = '[{},{"staticSize":12, "dynamicSize":0}]' + + [cases.tb_all_fail_crashes] + oil_skip = "oil doesn't handle invalid strings" + param_types = ["const std::string&", "const std::string&"] + args = "arg0,arg1" + setup = """ + // Create a string with an invalid size/capacity to trip TreeBuilder + StringInner strIn1{0, (uintptr_t)-1, (uintptr_t)-1, 0}; + std::string *str1 = (std::string*)&strIn1; + + StringInner strIn2{0, (uintptr_t)-1, (uintptr_t)-1, 0}; + std::string *str2 = (std::string*)&strIn2; + + return { std::move(*str1), std::move(*str2) }; + """ + expect_oid_exit_code = 6 + expect_stderr = ".*Nothing to output: failed to run TreeBuilder on any argument.*" diff --git a/test/integration/namespaces.toml b/test/integration/namespaces.toml new file mode 100644 index 0000000..6100457 --- /dev/null +++ b/test/integration/namespaces.toml @@ -0,0 +1,31 @@ +# This test checks that we can correctly distinguish between types with the same +# name in different namespaces. +includes = ["queue", "stack"] +definitions = ''' + namespace nsA { + struct Foo { + int x; + }; + } // namespace nsA + namespace nsB { + struct Foo { + int y; + int z; + }; + } // namespace nsB +''' +[cases] + [cases.queue] + param_types = ["const std::queue>&"] + setup = "return std::queue>({{ns_namespaces::nsA::Foo(), ns_namespaces::nsB::Foo()}});" + expect_json = '''[{ + "typeName": "queue, std::deque, std::allocator > > >", + "staticSize": 80, "dynamicSize": 12, "length": 1, "capacity": 1, "elementStaticSize": 12 + }]''' + [cases.stack] + param_types = ["const std::stack>&"] + setup = "return std::stack>({{ns_namespaces::nsA::Foo(), ns_namespaces::nsB::Foo()}});" + expect_json = '''[{ + "typeName": "stack, std::deque, std::allocator > > >", + "staticSize": 80, "dynamicSize": 12, "length": 1, "capacity": 1, "elementStaticSize": 12 + }]''' diff --git a/test/integration/packed.toml b/test/integration/packed.toml new file mode 100644 index 0000000..2bbef02 --- /dev/null +++ b/test/integration/packed.toml @@ -0,0 +1,19 @@ +definitions = ''' + struct __attribute__((__packed__)) Foo { + char *p; /* 8 bytes */ + char c; /* 1 byte */ + long x; /* 8 bytes */ + }; +''' +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = "return {};" + expect_json = '''[{ + "staticSize":17, + "dynamicSize":0, + "members":[ + {"name":"p", "staticSize":8, "dynamicSize":0}, + {"name":"c", "staticSize":1, "dynamicSize":0}, + {"name":"x", "staticSize":8, "dynamicSize":0} + ]}]''' diff --git a/test/integration/padding.toml b/test/integration/padding.toml new file mode 100644 index 0000000..13000fe --- /dev/null +++ b/test/integration/padding.toml @@ -0,0 +1,102 @@ +definitions = ''' + struct Foo { + int *a; + bool b; + long c; + }; + + struct Bar { + int *a; + bool b; + long c; + Foo d; + }; + + /* The names generated for parent's padding use their own index, + * which can conflict with the child's generated name. + * We must ensure there are no such conflicts, even across multiple parents. + */ + struct PaddedGrandParentA { + bool x; + short y; + }; + + struct PaddedGrandParentB { + bool x; + int y; + }; + + struct PaddedParentA : public PaddedGrandParentA { + bool a; + int b; + short c; + }; + + struct PaddedParentB : public PaddedGrandParentA, public PaddedGrandParentB { + bool a; + long b; + }; + + /* Create lots of padding holes so there is a colision between the child and + * its parent generated padding names. + */ + struct PaddedChild : public PaddedParentA, public PaddedParentB { + bool a; + long long b; + bool c; short d; + bool e; short f; + bool g; short h; + bool i; short j; + bool k; short l; + bool m; short n; + bool o; short p; + bool q; short r; + bool s; short t; + bool u; short v; + bool w; short x; + bool y; short z; + }; +''' + +[cases] + [cases.bool_padding] + param_types = ["const Foo&"] + setup = "return Foo{0, false, 0};" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":0, + "members":[ + { "name":"a", "staticSize":8, "dynamicSize":0 }, + { "name":"b", "staticSize":1, "dynamicSize":0 }, + { "name":"c", "staticSize":8, "dynamicSize":0 } + ]}]''' + + [cases.nested_padding] + param_types = ["const Bar&"] + setup = "return Bar{0, false, 0, Foo { 0, false, 0 }};" + expect_json = '''[{ + "staticSize":48, + "dynamicSize":0, + "members":[ + { "name":"a", "staticSize":8, "dynamicSize":0 }, + { "name":"b", "staticSize":1, "dynamicSize":0 }, + { "name":"c", "staticSize":8, "dynamicSize":0 }, + { + "name":"d", + "staticSize":24, + "dynamicSize":0, + "members": [ + { "name":"a", "staticSize":8, "dynamicSize":0 }, + { "name":"b", "staticSize":1, "dynamicSize":0 }, + { "name":"c", "staticSize":8, "dynamicSize":0 } + ]} + ]}]''' + + [cases.parent_padding] + param_types = ["const PaddedChild&"] + setup = "return PaddedChild{};" + expect_json = '''[{ + "staticSize": 104, + "dynamicSize": 0, + "paddingSavingsSize": 19 + }]''' diff --git a/test/integration/pointers.toml b/test/integration/pointers.toml new file mode 100644 index 0000000..e08988a --- /dev/null +++ b/test/integration/pointers.toml @@ -0,0 +1,238 @@ +includes = ["vector"] + +definitions = ''' + struct PrimitivePtrs { + int a; + int *b; + void *c; // No dynamic size, we can't know what it points to! + }; + + struct VectorPtr { + std::vector *vec; + }; +''' + +[cases] + [cases.int] + skip = "top-level pointers are skipped over" + oil_skip = "oil can't chase pointers safely" + param_types = ["int*"] + setup = "return new int(1);" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "typeName": "int *", + "staticSize": 8, + "dynamicSize": 4, + "NOT": {"pointer": 0}, + "members": [ + { + "typeName": "int", + "staticSize": 4, + "dynamicSize": 0 + } + ] + }]''' + [cases.int_no_follow] + skip = "top-level pointers are skipped over" + param_types = ["int*"] + setup = "return new int(1);" + expect_json = '''[{ + "typeName": "int *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.int_null] + skip = "top-level pointers are skipped over" + param_types = ["int*"] + setup = "return nullptr;" + expect_json = '''[{ + "typeName": "int *", + "staticSize": 8, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }]''' + + + [cases.void] + skip = "top-level pointers are skipped over" + param_types = ["void*"] + setup = "return new int(1);" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "typeName": "void *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.void_no_follow] + skip = "top-level pointers are skipped over" + param_types = ["void*"] + setup = "return new int(1);" + expect_json = '''[{ + "typeName": "void *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.void_null] + skip = "top-level pointers are skipped over" + param_types = ["void*"] + setup = "return nullptr;" + expect_json = '''[{ + "typeName": "void *", + "staticSize": 8, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }]''' + + + [cases.vector] + skip = "top-level pointers are skipped over" + oil_skip = "oil can't chase pointers safely" + param_types = ["std::vector*"] + setup = "return new std::vector{1,2,3};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "typeName": "std::vector *", + "staticSize": 8, + "dynamicSize": 36, + "NOT": {"pointer": 0}, + "members": [ + { + "typeName": "std::vector", + "staticSize": 24, + "dynamicSize": 12 + } + ] + }]''' + [cases.vector_no_follow] + skip = "top-level pointers are skipped over" + param_types = ["std::vector*"] + setup = "return new std::vector{1,2,3};" + expect_json = '''[{ + "typeName": "std::vector *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.vector_null] + skip = "BAD DATA SEGMENT!!! top-level pointers are skipped over" + param_types = ["std::vector*"] + setup = "return nullptr;" + expect_json = '''[{ + "typeName": "std::vector *", + "staticSize": 8, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }]''' + + + [cases.struct_primitive_ptrs] + oil_skip = "oil can't chase pointers safely" + param_types = ["const PrimitivePtrs&"] + setup = "return PrimitivePtrs{0, new int(0), new int(0)};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":24, + "dynamicSize":4, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0}, + {"name":"b", "staticSize":8, "dynamicSize":4}, + {"name":"c", "staticSize":8, "dynamicSize":0} + ]}]''' + [cases.struct_primitive_ptrs_no_follow] + param_types = ["const PrimitivePtrs&"] + setup = "return PrimitivePtrs{0, new int(0), new int(0)};" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":0, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0}, + {"name":"b", "staticSize":8, "dynamicSize":0}, + {"name":"c", "staticSize":8, "dynamicSize":0} + ]}]''' + [cases.struct_primitive_ptrs_null] + param_types = ["const PrimitivePtrs&"] + setup = "return PrimitivePtrs{0, nullptr, nullptr};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":24, + "dynamicSize":0, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0}, + {"name":"b", "staticSize":8, "dynamicSize":0}, + {"name":"c", "staticSize":8, "dynamicSize":0} + ]}]''' + + + [cases.struct_vector_ptr] + oil_skip = "oil can't chase pointers safely" + param_types = ["const VectorPtr&"] + setup = "return VectorPtr{new std::vector{1,2,3}};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":8, + "dynamicSize":36, + "members":[ + {"name":"vec", "staticSize":8, "dynamicSize":36} + ]}]''' + [cases.struct_vector_ptr_no_follow] + param_types = ["const VectorPtr&"] + setup = "return VectorPtr{new std::vector{1,2,3}};" + expect_json = '''[{ + "staticSize":8, + "dynamicSize":0, + "members":[ + {"name":"vec", "staticSize":8, "dynamicSize":0} + ]}]''' + [cases.struct_vector_ptr_null] + param_types = ["const VectorPtr&"] + setup = "return VectorPtr{nullptr};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":8, + "dynamicSize":0, + "members":[ + {"name":"vec", "staticSize":8, "dynamicSize":0} + ]}]''' + + + [cases.vector_of_pointers] + oil_skip = "oil can't chase pointers safely" + param_types = ["const std::vector&"] + setup = "return {{new int(1), nullptr, new int(3)}};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize":24, + "dynamicSize":32, + "length":3, + "capacity":3, + "elementStaticSize":8, + "members":[ + {"staticSize":8, "dynamicSize":4, "NOT": {"pointer":0}}, + {"staticSize":8, "dynamicSize":0, "pointer":0}, + {"staticSize":8, "dynamicSize":4, "NOT": {"pointer":0}} + ]}]''' + [cases.vector_of_pointers_no_follow] + oid_skip = "pointer field is missing from results" + param_types = ["const std::vector&"] + setup = "return {{new int(1), nullptr, new int(3)}};" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":24, + "length":3, + "capacity":3, + "elementStaticSize":8, + "members":[ + {"staticSize":8, "dynamicSize":0, "NOT": {"pointer":0}}, + {"staticSize":8, "dynamicSize":0, "pointer":0}, + {"staticSize":8, "dynamicSize":0, "NOT": {"pointer":0}} + ]}]''' diff --git a/test/integration/pointers_function.toml b/test/integration/pointers_function.toml new file mode 100644 index 0000000..2c0d24c --- /dev/null +++ b/test/integration/pointers_function.toml @@ -0,0 +1,96 @@ +includes = ["functional"] + +definitions = ''' + void myFunction(int x) { + (void)x; + } + + // Put this function pointer inside a struct since the test framework doesn't + // support the syntax required for passing raw function pointers as arguments + struct FuncPtrStruct { + void (*p)(int); + }; +''' + +[cases] + [cases.raw] + skip = "function pointers are not handled correctly" + param_types = ["const FuncPtrStruct&"] + setup = "return {{myFunction}};" + expect_json = '''[{ + "staticSize": 8, + "dynamicSize": 0, + "members": [{ + "typeName": "void (*)(int)", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }] + }]''' + [cases.raw_chase] # We should never chase function pointers + skip = "function pointers are not handled correctly" + param_types = ["const FuncPtrStruct&"] + setup = "return {{myFunction}};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 8, + "dynamicSize": 0, + "members": [{ + "typeName": "void (*)(int)", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }] + }]''' + [cases.raw_null] + skip = "function pointers are not handled correctly" + param_types = ["const FuncPtrStruct&"] + setup = "return {{nullptr}};" + expect_json = '''[{ + "staticSize": 8, + "dynamicSize": 0, + "members": [{ + "typeName": "void (*)(int)", + "staticSize": 8, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }] + }]''' + + [cases.std_function] + skip = "function pointers are not handled correctly" + param_types = ["std::function &"] + setup = "return myFunction;" + expect_json = '''[{ + "typeName": "function", + "staticSize": 32, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.std_function_chase] # We should never chase function pointers + skip = "function pointers are not handled correctly" + param_types = ["std::function &"] + setup = "return myFunction;" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "typeName": "function", + "staticSize": 32, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.std_function_null] + skip = "function pointers are not handled correctly" + param_types = ["std::function &"] + setup = "return nullptr;" + expect_json = '''[{ + "typeName": "function", + "staticSize": 32, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }]''' diff --git a/test/integration/pointers_incomplete.toml b/test/integration/pointers_incomplete.toml new file mode 100644 index 0000000..50e64e0 --- /dev/null +++ b/test/integration/pointers_incomplete.toml @@ -0,0 +1,114 @@ +includes = ["memory", "optional"] + +definitions = ''' + struct IncompleteType; + struct IncompleteTypeContainer { + IncompleteType *ptrundef; + // std::unique_ptr unundef; // std::unique_ptr requires its template param to have a size + char __makePad1; + std::shared_ptr shundef; + char __makePad2; + std::optional> shoptundef; + char __makePad3; + std::optional optundef; + }; + + void incomplete_type_deleter(IncompleteType *ptr) { + ::operator delete(ptr); + } +''' +[cases] + [cases.raw] + skip = "codegen fails on this" + param_types = ["IncompleteType*"] + setup = "return static_cast(::operator new(5));" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "typeName": "IncompleteType *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.raw_no_follow] + skip = "codegen fails on this" + param_types = ["IncompleteType*"] + setup = "return static_cast(::operator new(5));" + expect_json = '''[{ + "typeName": "IncompleteType *", + "staticSize": 8, + "dynamicSize": 0, + "NOT": {"pointer": 0}, + "NOT": "members" + }]''' + [cases.raw_null] + skip = "codegen fails on this" + param_types = ["IncompleteType*"] + setup = "return nullptr;" + expect_json = '''[{ + "typeName": "IncompleteType *", + "staticSize": 8, + "dynamicSize": 0, + "pointer": 0, + "NOT": "members" + }]''' + + [cases.unique_ptr] + param_types = ["const std::unique_ptr&"] + setup = ''' + auto raw_ptr = static_cast(::operator new(5)); + return std::unique_ptr( + raw_ptr, &incomplete_type_deleter); + ''' + expect_json = '[{"staticSize":16, "dynamicSize":0, "NOT":"members"}]' + [cases.unique_ptr_null] + param_types = ["const std::unique_ptr&"] + setup = ''' + return std::unique_ptr( + nullptr, &incomplete_type_deleter); + ''' + expect_json = '[{"staticSize":16, "dynamicSize":0, "NOT":"members"}]' + + [cases.shared_ptr] + param_types = ["const std::shared_ptr&"] + setup = ''' + auto raw_ptr = static_cast(::operator new(5)); + return std::shared_ptr(raw_ptr , &incomplete_type_deleter); + ''' + expect_json = '[{"staticSize":16, "dynamicSize":0, "NOT":"members"}]' + [cases.shared_ptr_null] + param_types = ["const std::shared_ptr"] + setup = "return nullptr;" + expect_json = '[{"staticSize":16, "dynamicSize":0, "NOT":"members"}]' + + [cases.containing_struct] + oil_skip = "oil can't chase pointers safely" + param_types = ["const IncompleteTypeContainer&"] + setup = "return IncompleteTypeContainer{};" + cli_options = ["--chase-raw-pointers"] + expect_json = '''[{ + "staticSize": 88, + "dynamicSize": 0, + "paddingSavingsSize": 21, + "members": [ + { "name": "ptrundef", "staticSize": 8, "dynamicSize": 0 }, + { "name": "__makePad1", "staticSize": 1, "dynamicSize": 0 }, + { "name": "shundef", "staticSize": 16, "dynamicSize": 0 }, + { "name": "__makePad2", "staticSize": 1, "dynamicSize": 0 }, + { "name": "shoptundef", + "staticSize": 24, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 16 + }, + { "name": "__makePad3", "staticSize": 1, "dynamicSize": 0 }, + { "name": "optundef", + "staticSize": 16, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 8 + } + ] + }]''' diff --git a/test/integration/primitives.toml b/test/integration/primitives.toml new file mode 100644 index 0000000..0b49dc9 --- /dev/null +++ b/test/integration/primitives.toml @@ -0,0 +1,73 @@ +[cases] + [cases.short] + param_types = ["short"] + setup = "return 123;" + expect_json = '[{"staticSize":2, "dynamicSize":0}]' + [cases.ushort] + param_types = ["unsigned short"] + setup = "return 123;" + expect_json = '[{"staticSize":2, "dynamicSize":0}]' + [cases.int] + param_types = ["int"] + setup = "return 123;" + expect_json = '[{"staticSize":4, "dynamicSize":0}]' + [cases.uint] + param_types = ["unsigned int"] + setup = "return 123;" + expect_json = '[{"staticSize":4, "dynamicSize":0}]' + [cases.long] + param_types = ["long"] + setup = "return 123;" + expect_json = '[{"staticSize":8, "dynamicSize":0}]' + [cases.ulong] + param_types = ["unsigned long"] + setup = "return 123;" + expect_json = '[{"staticSize":8, "dynamicSize":0}]' + [cases.longlong] + param_types = ["long long"] + setup = "return 123;" + expect_json = '[{"staticSize":8, "dynamicSize":0}]' + [cases.ulonglong] + param_types = ["unsigned long long"] + setup = "return 123;" + expect_json = '[{"staticSize":8, "dynamicSize":0}]' + [cases.bool] + param_types = ["bool"] + setup = "return true;" + expect_json = '[{"staticSize":1, "dynamicSize":0}]' + [cases.char] + param_types = ["char"] + setup = "return 'a';" + expect_json = '[{"staticSize":1, "dynamicSize":0}]' + [cases.uchar] + param_types = ["unsigned char"] + setup = "return 'a';" + expect_json = '[{"staticSize":1, "dynamicSize":0}]' + [cases.schar] + param_types = ["signed char"] + setup = "return 'a';" + expect_json = '[{"staticSize":1, "dynamicSize":0}]' + [cases.wchar_t] + param_types = ["wchar_t"] + setup = "return 'a';" + expect_json = '[{"staticSize":4, "dynamicSize":0}]' + [cases.char16_t] + param_types = ["char16_t"] + setup = "return 'a';" + expect_json = '[{"staticSize":2, "dynamicSize":0}]' + [cases.char32_t] + param_types = ["char32_t"] + setup = "return 'a';" + expect_json = '[{"staticSize":4, "dynamicSize":0}]' + [cases.float] + param_types = ["float"] + setup = "return 3.14;" + expect_json = '[{"staticSize":4, "dynamicSize":0}]' + [cases.double] + param_types = ["double"] + setup = "return 3.14;" + expect_json = '[{"staticSize":8, "dynamicSize":0}]' + [cases.long_double] + param_types = ["long double"] + setup = "return 3.14;" + expect_json = '[{"staticSize":16, "dynamicSize":0}]' diff --git a/test/integration/references.toml b/test/integration/references.toml new file mode 100644 index 0000000..d989919 --- /dev/null +++ b/test/integration/references.toml @@ -0,0 +1,50 @@ +includes = ["vector"] +definitions = ''' + struct IntRef { + int &n; + }; + struct VectorRef { + std::vector &vec; + }; +''' +[cases] + [cases.int_ref] + skip = "references are being treated as raw pointers" + param_types = ["const IntRef&"] + setup = "return {{*new int(1)}};" + expect_json = '''[{ + "staticSize":8, + "dynamicSize":4, + "members":[ + { + "typeName": "int &", + "name": "n", + "staticSize": 8, + "dynamicSize": 4 + } + ]}]''' + [cases.vector_ref] + skip = "references are being treated as raw pointers" + param_types = ["const VectorRef&"] + setup = "return {{*new std::vector{1,2,3}}};" + expect_json = '''[{ + "staticSize":8, + "dynamicSize":36, + "members":[ + { + "typeName": "std::vector &", + "name": "vec", + "staticSize": 8, + "dynamicSize": 36, + "members": [ + { + "typeName": "std::vector", + "staticSize":24, + "dynamicSize":12, + "length":3, + "capacity":3, + "elementStaticSize":4 + } + ] + } + ]}]''' diff --git a/test/integration/runner_common.cpp b/test/integration/runner_common.cpp new file mode 100644 index 0000000..5ea2cb8 --- /dev/null +++ b/test/integration/runner_common.cpp @@ -0,0 +1,358 @@ +#include "runner_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OIOpts.h" + +using namespace std::literals; + +namespace bp = boost::process; +namespace bpt = boost::property_tree; +namespace fs = std::filesystem; + +std::string oidExe = OID_EXE_PATH; +std::string configFile = CONFIG_FILE_PATH; + +bool verbose = false; +bool preserve = false; +bool preserve_on_failure = false; +bool run_skipped_tests = false; + +constexpr static OIOpts opts{ + OIOpt{'h', "help", no_argument, nullptr, "Print this message and exit"}, + OIOpt{'p', "preserve", no_argument, nullptr, + "Do not clean up files generated by OID after tests are finished"}, + OIOpt{'P', "preserve-on-failure", no_argument, nullptr, + "Do not clean up files generated by OID for failed tests"}, + OIOpt{'v', "verbose", no_argument, nullptr, + "Verbose output. Show OID's stdout and stderr on test failure"}, + OIOpt{'f', "force", no_argument, nullptr, + "Force running tests, even if they are marked as skipped"}, + OIOpt{'x', "oid", required_argument, nullptr, + "Path to OID executable to test"}, +}; + +void usage(std::string_view progname) { + std::cout << "usage: " << progname << " ...\n"; + std::cout << opts; +} + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + int c; + while ((c = getopt_long(argc, argv, opts.shortOpts(), opts.longOpts(), + nullptr)) != -1) { + switch (c) { + case 'p': + preserve = true; + break; + case 'P': + preserve_on_failure = true; + break; + case 'v': + verbose = true; + break; + case 'f': + run_skipped_tests = true; + break; + case 'x': + // Must convert to absolute path so it continues to work after working + // directory is changed + oidExe = fs::absolute(optarg); + break; + case 'h': + default: + usage(argv[0]); + return 0; + } + } + + return RUN_ALL_TESTS(); +} + +void IntegrationBase::SetUp() { + // Move into a temporary directory to run the test in an isolated environment + auto tmp_dir = TmpDirStr(); + char *res = mkdtemp(&tmp_dir[0]); + if (!res) + abort(); + workingDir = std::move(tmp_dir); + fs::current_path(workingDir); +} + +void IntegrationBase::TearDown() { + const ::testing::TestInfo *const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + if (preserve || (preserve_on_failure && test_info->result()->Failed())) { + std::cerr << "Working directory preserved at: " << workingDir << std::endl; + } else { + fs::remove_all(workingDir); + } +} + +int IntegrationBase::exit_code(Proc &proc) { + proc.ctx.run(); + proc.proc.wait(); + + // Read stdout and stderr into members, as required by certain tests (see + // gen_tests.py) + { + std::ifstream stdout(workingDir / "stdout"); + stdout_.assign(std::istreambuf_iterator(stdout), + std::istreambuf_iterator()); + } + { + std::ifstream stderr(workingDir / "stderr"); + stderr_.assign(std::istreambuf_iterator(stderr), + std::istreambuf_iterator()); + } + return proc.proc.exit_code(); +} + +std::string OidIntegration::TmpDirStr() { + return std::string("/tmp/oid-integration-XXXXXX"); +} + +OidProc OidIntegration::runOidOnProcess(OidOpts opts, + std::vector extra_args, + std::string extra_config) { + // Binary paths are populated by CMake + std::string targetExe = std::string(TARGET_EXE_PATH) + " " + opts.targetArgs; + + /* Spawn the target process with all IOs redirected to /dev/null to not polute + * the terminal */ + // clang-format off + bp::child targetProcess( + targetExe, + bp::std_in < bp::null, + bp::std_out > bp::null, + bp::std_err > bp::null, + opts.ctx); + // clang-format on + + { + // Delete previous segconfig, in case oid re-used the PID + fs::path segconfigPath = + "/tmp/oid-segconfig-" + std::to_string(targetProcess.id()); + fs::remove(segconfigPath); + // Create the segconfig, so we have the rights to delete it above + std::ofstream touch(segconfigPath); + } + + // Only create a new custom config if we've been provided an extra_config + boost::trim(extra_config); + + fs::path customConfigFile = configFile; + if (!extra_config.empty()) { + customConfigFile = workingDir / "oid.config.toml"; + fs::copy_file(configFile, customConfigFile); + + std::ofstream customConfig(customConfigFile, std::ios_base::app); + customConfig << "\n\n# Test custom config\n\n"; + customConfig << extra_config; + } + + // Keep PID as the last argument to make it easier for users to directly copy + // and modify the command from the verbose mode output. + // clang-format off + auto default_args = std::array{ + "--debug-level=3"s, + "--timeout=20"s, + "--dump-json"s, + "--config-file"s, customConfigFile.string(), + "--script-source"s, opts.scriptSource, + "--pid"s, std::to_string(targetProcess.id()), + }; + // clang-format on + auto oid_args = extra_args; + oid_args.insert(oid_args.end(), default_args.begin(), default_args.end()); + + if (verbose) { + std::cerr << "Running: " << targetExe << "\n"; + std::cerr << "Running: " << oidExe << " "; + for (const auto &arg : oid_args) { + std::cerr << arg << " "; + } + std::cerr << std::endl; + } + + // Use tee to write the output to files. If verbose is on, also redirect the + // output to stderr. + bp::async_pipe std_out_pipe(opts.ctx), std_err_pipe(opts.ctx); + bp::child std_out, std_err; + if (verbose) { + // clang-format off + std_out = bp::child(bp::search_path("tee"), + (workingDir / "stdout").string(), + bp::std_in < std_out_pipe, + bp::std_out > stderr, + opts.ctx); + std_err = bp::child(bp::search_path("tee"), + (workingDir / "stderr").string(), + bp::std_in < std_err_pipe, + bp::std_out > stderr, + opts.ctx); + // clang-format on + } else { + // clang-format off + std_out = bp::child(bp::search_path("tee"), + (workingDir / "stdout").string(), + bp::std_in < std_out_pipe, + bp::std_out > bp::null, + opts.ctx); + std_err = bp::child(bp::search_path("tee"), + (workingDir / "stderr").string(), + bp::std_in < std_err_pipe, + bp::std_out > bp::null, + opts.ctx); + // clang-format on + } + + /* Spawn `oid` with tracing on and IOs redirected */ + // clang-format off + bp::child oidProcess( + oidExe, + bp::env["OID_METRICS_TRACE"] = "time", + bp::args(oid_args), + bp::std_in < bp::null, + bp::std_out > std_out_pipe, + bp::std_err > std_err_pipe, + opts.ctx); + // clang-format on + + return OidProc{ + .target = Proc{opts.ctx, std::move(targetProcess), {}, {}}, + .oid = Proc{opts.ctx, std::move(oidProcess), std::move(std_out), + std::move(std_err)}, + }; +} + +void OidIntegration::compare_json(const bpt::ptree &expected_json, + const bpt::ptree &actual_json, + const std::string &full_key, bool expect_eq) { + if (expected_json.empty()) { + if (expect_eq) { + ASSERT_EQ(expected_json.data(), actual_json.data()) + << "Incorrect value for key: " << full_key; + } else { + ASSERT_NE(expected_json.data(), actual_json.data()) + << "Incorrect value for key: " << full_key; + } + } + + if (expected_json.begin()->first == "") { + // Empty key - assume this is an array + + ASSERT_EQ(expected_json.size(), actual_json.size()) + << "Array size difference for key: " << full_key; + + int i = 0; + for (auto e_it = expected_json.begin(), a_it = actual_json.begin(); + e_it != expected_json.end() && a_it != actual_json.end(); + e_it++, a_it++) { + compare_json(e_it->second, a_it->second, + full_key + "[" + std::to_string(i) + "]", expect_eq); + i++; + } + return; + } + + // Compare as Key-Value pairs + for (const auto &[key, val] : expected_json) { + if (key == "NOT") { + auto curr_key = full_key + ".NOT"; + ASSERT_EQ(true, expect_eq) << "Invalid expected data: " << curr_key + << " - Can not use nested \"NOT\" expressions"; + if (val.empty()) { + // Check that a given single key does not exist + const auto &key_to_check = val.data(); + auto actual_it = actual_json.find(key_to_check); + auto curr_key = full_key + "." + key_to_check; + if (actual_it != actual_json.not_found()) { + ADD_FAILURE() << "Unexpected key found in output: " << curr_key; + } + } else { + // Check that a key's value is not equal to something + compare_json(val, actual_json, curr_key, !expect_eq); + } + continue; + } + + auto actual_it = actual_json.find(key); + auto curr_key = full_key + "." + key; + if (actual_it == actual_json.not_found()) { + ADD_FAILURE() << "Expected key not found in output: " << curr_key; + continue; + } + compare_json(val, actual_it->second, curr_key, expect_eq); + } +} + +std::string OilIntegration::TmpDirStr() { + return std::string("/tmp/oil-integration-XXXXXX"); +} + +Proc OilIntegration::runOilTarget(OidOpts opts) { + std::string targetExe = std::string(TARGET_EXE_PATH) + " " + opts.targetArgs; + + if (verbose) { + std::cerr << "Running: " << targetExe << std::endl; + } + + // Use tee to write the output to files. If verbose is on, also redirect the + // output to stderr. + bp::async_pipe std_out_pipe(opts.ctx), std_err_pipe(opts.ctx); + bp::child std_out, std_err; + if (verbose) { + // clang-format off + std_out = bp::child(bp::search_path("tee"), + (workingDir / "stdout").string(), + bp::std_in < std_out_pipe, + bp::std_out > stderr, + opts.ctx); + std_err = bp::child(bp::search_path("tee"), + (workingDir / "stderr").string(), + bp::std_in < std_err_pipe, + bp::std_out > stderr, + opts.ctx); + // clang-format on + } else { + // clang-format off + std_out = bp::child(bp::search_path("tee"), + (workingDir / "stdout").string(), + bp::std_in < std_out_pipe, + bp::std_out > bp::null, + opts.ctx); + std_err = bp::child(bp::search_path("tee"), + (workingDir / "stderr").string(), + bp::std_in < std_err_pipe, + bp::std_out > bp::null, + opts.ctx); + // clang-format on + } + + /* Spawn target with tracing on and IOs redirected in custom pipes to be read + * later */ + // clang-format off + bp::child targetProcess( + targetExe, + bp::std_in < bp::null, + bp::std_out > std_out_pipe, + bp::std_err > std_err_pipe, + bp::env["CONFIG_FILE_PATH"] = configFile, + opts.ctx); + // clang-format on + + return Proc{opts.ctx, std::move(targetProcess), std::move(std_out), + std::move(std_err)}; +} diff --git a/test/integration/runner_common.h b/test/integration/runner_common.h new file mode 100644 index 0000000..fc23aaa --- /dev/null +++ b/test/integration/runner_common.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +struct OidOpts { + boost::asio::io_context &ctx; + std::string targetArgs; + std::string script; + std::string scriptSource; +}; + +struct Proc { + boost::asio::io_context &ctx; + boost::process::child proc; + boost::process::child tee_stdout; + boost::process::child tee_stderr; +}; + +struct OidProc { + Proc target; + Proc oid; +}; + +class IntegrationBase : public ::testing::Test { + protected: + virtual ~IntegrationBase() = default; + + virtual std::string TmpDirStr() = 0; + + void TearDown() override; + void SetUp() override; + int exit_code(Proc &proc); + + std::filesystem::path workingDir; + + std::string stdout_; + std::string stderr_; +}; + +class OidIntegration : public IntegrationBase { + protected: + std::string TmpDirStr() override; + + OidProc runOidOnProcess(OidOpts opts, std::vector extra_args, + std::string extra_config); + + /* + * compare_json + * + * Compares two JSON objects for equality if "expect_eq" is true, or for + * inequality if "expect_eq" is false. + */ + void compare_json(const boost::property_tree::ptree &expected_json, + const boost::property_tree::ptree &actual_json, + const std::string &full_key = "root", + bool expect_eq = true); +}; + +class OilIntegration : public IntegrationBase { + protected: + std::string TmpDirStr() override; + + Proc runOilTarget(OidOpts opts); +}; diff --git a/test/integration/simple_multiple_multilevel_inheritance.toml b/test/integration/simple_multiple_multilevel_inheritance.toml new file mode 100644 index 0000000..f730ac9 --- /dev/null +++ b/test/integration/simple_multiple_multilevel_inheritance.toml @@ -0,0 +1,39 @@ +definitions = ''' + struct Base_1 { + int a; + }; + struct Base_2 { + int b; + }; + struct Base_3 { + int c; + }; + struct Derived_1: Base_2, Base_3 { + int d; + }; + struct Base_4 { + int e; + }; + struct Derived_2: Base_1, Derived_1, Base_4 { + int f; + }; +''' + +[cases] + [cases.a] + param_types = ["const Derived_2&"] + setup = ''' + Derived_2 d; + return {d}; + ''' + expect_json = '''[{ + "staticSize":24, + "dynamicSize":0, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0, "typeName": "int"}, + {"name":"b", "staticSize":4, "dynamicSize":0, "typeName": "int"}, + {"name":"c", "staticSize":4, "dynamicSize":0, "typeName": "int"}, + {"name":"d", "staticSize":4, "dynamicSize":0, "typeName": "int"}, + {"name":"e", "staticSize":4, "dynamicSize":0, "typeName": "int"}, + {"name":"f", "staticSize":4, "dynamicSize":0, "typeName": "int"} + ]}]''' diff --git a/test/integration/simple_struct.toml b/test/integration/simple_struct.toml new file mode 100644 index 0000000..de1cbaa --- /dev/null +++ b/test/integration/simple_struct.toml @@ -0,0 +1,20 @@ +definitions = ''' + struct Foo { + int a; + int b; + int c; + }; +''' + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = "return {};" + expect_json = '''[{ + "staticSize":12, + "dynamicSize":0, + "members":[ + {"name":"a", "staticSize":4, "dynamicSize":0}, + {"name":"b", "staticSize":4, "dynamicSize":0}, + {"name":"c", "staticSize":4, "dynamicSize":0} + ]}]''' diff --git a/test/integration/std_array.toml b/test/integration/std_array.toml new file mode 100644 index 0000000..83fdb92 --- /dev/null +++ b/test/integration/std_array.toml @@ -0,0 +1,88 @@ +includes = ["array", "cstdint", "vector"] +[cases] + [cases.array_uint64_length_0] + param_types = ["std::array&"] + setup = "return {{}};" + expect_json = ''' + [ + { + "staticSize": 1, + "dynamicSize": 0, + "length": 0, + "capacity": 0, + "elementStaticSize": 8 + } + ] + ''' + [cases.array_uint64_length_1] + param_types = ["std::array&"] + setup = "return {{1}};" + expect_json = ''' + [ + { + "staticSize": 8, + "dynamicSize": 0, + "length": 1, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.array_uint64_length_8] + param_types = ["std::array&"] + setup = "return {{0,1,2,3,4,5,6,7}};" + expect_json = ''' + [ + { + "staticSize": 64, + "dynamicSize": 0, + "length": 8, + "capacity": 8, + "elementStaticSize": 8 + } + ] + ''' + [cases.array_vector_length_1] + param_types = ["std::array, 1>&"] + setup = "return {{std::initializer_list({1,2,3,4,5})}};" + expect_json = ''' + [ + { + "staticSize": 24, + "dynamicSize": 40, + "length": 1, + "capacity": 1, + "elementStaticSize": 24, + "members": [ + { + "staticSize": 24, + "dynamicSize": 40 + } + ] + } + ] + ''' + [cases.array_vector_length_2] + param_types = ["std::array, 2>&"] + setup = "return {{std::initializer_list({1,2,3,4,5}), std::initializer_list({6,7,8,9})}};" + expect_json = ''' + [ + { + "staticSize": 48, + "dynamicSize": 72, + "length": 2, + "capacity": 2, + "elementStaticSize": 24, + "members": [ + { + "staticSize": 24, + "dynamicSize": 40 + }, + { + "staticSize": 24, + "dynamicSize": 32 + } + ] + } + ] + ''' diff --git a/test/integration/std_deque.toml b/test/integration/std_deque.toml new file mode 100644 index 0000000..ec24b26 --- /dev/null +++ b/test/integration/std_deque.toml @@ -0,0 +1,30 @@ +# TODO deque capacity: https://github.com/facebookexperimental/object-introspection/issues/637 +# 8 times the object size on 64-bit libstdc++; 16 times the object size or 4096 bytes, whichever is larger, on 64-bit libc++ +includes = ["deque"] +[cases] + [cases.int_empty] + param_types = ["const std::deque&"] + setup = "return {};" + expect_json = '[{"staticSize":80, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":4}]' + [cases.int_some] + param_types = ["const std::deque&"] + setup = "return {{1,2,3}};" + expect_json = '[{"staticSize":80, "dynamicSize":12, "length":3, "capacity":3, "elementStaticSize":4}]' + [cases.deque_int_empty] + param_types = ["const std::deque>&"] + setup = "return {};" + expect_json = '[{"staticSize":80, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":80}]' + [cases.deque_int_some] + param_types = ["const std::deque>&"] + setup = "return {{{1,2,3},{},{4,5}}};" + expect_json = '''[{ + "staticSize":80, + "dynamicSize":260, + "length":3, + "capacity":3, + "elementStaticSize":80, + "members":[ + {"staticSize":80, "dynamicSize":12, "length":3, "capacity":3, "elementStaticSize":4}, + {"staticSize":80, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":4}, + {"staticSize":80, "dynamicSize":8, "length":2, "capacity":2, "elementStaticSize":4} + ]}]''' diff --git a/test/integration/std_deque_del_allocator.toml b/test/integration/std_deque_del_allocator.toml new file mode 100644 index 0000000..96cda64 --- /dev/null +++ b/test/integration/std_deque_del_allocator.toml @@ -0,0 +1,56 @@ +definitions = ''' + namespace nsA { + struct C { + int a; + }; + } + namespace nsB { + struct C { + int b; + }; + } + + // TODO: Custom allocator for deque is generating a compiler error + + // deque generates compiler errors with custom allocator. But because of + // name conflicts, OI would fail unless it deletes the allocator param. + struct Foo { + std::deque v1; + std::deque v2; + }; + +''' +includes = ["deque"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + foo.v1.resize(1); + foo.v2.resize(2); + return {foo}; + ''' + expect_json = '''[{ + "staticSize":160, + "dynamicSize":12, + "members":[ + {"name":"v1", "staticSize":80, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"a", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]}, + {"name":"v2", "staticSize":80, "dynamicSize":8, "length":2, "capacity":2, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]}, + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]} + ]}]''' diff --git a/test/integration/std_list_del_allocator.toml b/test/integration/std_list_del_allocator.toml new file mode 100644 index 0000000..f15678f --- /dev/null +++ b/test/integration/std_list_del_allocator.toml @@ -0,0 +1,59 @@ +definitions = ''' + namespace nsA { + struct C { + int a; + }; + } + namespace nsB { + struct C { + int b; + }; + } + + template + class CustomAllocator: public std::allocator + { + + }; + + // Naming conflict will only work if OI deletes the allocator field + struct Foo { + std::list> v1; + std::list> v2; + }; + +''' +includes = ["list"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + foo.v1.resize(1); + foo.v2.resize(2); + return {foo}; + ''' + expect_json = '''[{ + "staticSize":48, + "dynamicSize":12, + "members":[ + {"name":"v1", "staticSize":24, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"a", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]}, + {"name":"v2", "staticSize":24, "dynamicSize":8, "length":2, "capacity":2, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]}, + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]} + ]}]''' diff --git a/test/integration/std_map_custom_comparator.toml b/test/integration/std_map_custom_comparator.toml new file mode 100644 index 0000000..4cca520 --- /dev/null +++ b/test/integration/std_map_custom_comparator.toml @@ -0,0 +1,69 @@ +definitions = ''' + struct CustomComparator { + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + // static size of map only increases in multiples of 8. So including + // a single double would keep the static size of map the same + struct SmallSizedCustomComparator { + double a; + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + struct BigSizedCustomComparator { + double d[1000]; + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + struct Foo { + std::map m1; + std::map m2; + std::map m3; + std::map m4; + }; + + struct Foo4 { + std::map> m; + }; +''' +includes = ["map", "functional"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + + for (int i = 0; i < 3; i++) { + foo.m1[i] = (i * 10); + } + + for (int i = 0; i < 5; i++) { + foo.m2[i] = (i * 10); + } + + for (int i = 0; i < 7; i++) { + foo.m3[i] = (i * 10); + } + + for (int i = 0; i < 9; i++) { + foo.m4[i] = (i * 10); + } + + return {foo}; + ''' + expect_json = '''[{ + "staticSize":8184, + "dynamicSize":768, + "members":[ + {"name":"m1", "staticSize":48, "dynamicSize":96, "length":3, "capacity":3, "elementStaticSize":32}, + {"name":"m2", "staticSize":48, "dynamicSize":160, "length":5, "capacity":5, "elementStaticSize":32}, + {"name":"m3", "staticSize":48, "dynamicSize":224, "length":7, "capacity":7, "elementStaticSize":32}, + {"name":"m4", "staticSize":8040, "dynamicSize":288, "length":9, "capacity":9, "elementStaticSize":32} + ]}]''' diff --git a/test/integration/std_optional.toml b/test/integration/std_optional.toml new file mode 100644 index 0000000..5329590 --- /dev/null +++ b/test/integration/std_optional.toml @@ -0,0 +1,65 @@ +includes = ["optional", "cstdint", "vector"] +[cases] + [cases.uint64_empty] + param_types = ["std::optional&"] + setup = "return std::nullopt;" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.uint64_present] + param_types = ["std::optional&"] + setup = "return 64;" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 1, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.vector_empty] + param_types = ["std::optional>&"] + setup = "return std::nullopt;" + expect_json = ''' + [ + { + "staticSize": 32, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 24 + } + ] + ''' + [cases.vector_present] + param_types = ["std::optional>&"] + setup = "return {{{1,2,3,4,5}}};" + expect_json = ''' + [ + { + "staticSize": 32, + "dynamicSize": 40, + "length": 1, + "capacity": 1, + "elementStaticSize": 24, + "members": [ + { + "staticSize": 24, + "dynamicSize": 40, + "length": 5 + } + ] + } + ] + ''' diff --git a/test/integration/std_pair.toml b/test/integration/std_pair.toml new file mode 100644 index 0000000..a509ecc --- /dev/null +++ b/test/integration/std_pair.toml @@ -0,0 +1,56 @@ +includes = ["vector", "utility", "cstdint"] +[cases] + [cases.uint64_uint64] + param_types = ["std::pair&"] + setup = "return {{0, 1}};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 1, + "capacity": 1 + } + ] + ''' + [cases.uint64_uint32] + param_types = ["std::pair&"] + setup = "return {{0, 1}};" + # Should still have static size of 16 due to padding + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 1, + "capacity": 1 + } + ] + ''' + [cases.vector_vector] + param_types = ["std::pair, std::vector>&"] + setup = "return {{std::initializer_list({0,1,2}), std::initializer_list({3,4,5,6})}};" + expect_json = ''' + [ + { + "staticSize": 48, + "dynamicSize": 56, + "length": 1, + "capacity": 1, + "members": [ + { + "staticSize": 24, + "dynamicSize": 24, + "length": 3, + "capacity": 3 + }, + { + "staticSize": 24, + "dynamicSize": 32, + "length": 4, + "capacity": 4 + } + ] + } + ] + ''' diff --git a/test/integration/std_priority_queue.toml b/test/integration/std_priority_queue.toml new file mode 100644 index 0000000..b50de9b --- /dev/null +++ b/test/integration/std_priority_queue.toml @@ -0,0 +1,50 @@ +includes = ["queue"] +[cases] + [cases.int_empty] + param_types = ["const std::priority_queue&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "priority_queue >, std::less >", + "staticSize": 32, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.int_some] + param_types = ["const std::priority_queue&"] + setup = "return std::priority_queue({}, {3,2,1});" + expect_json = '''[{ + "typeName": "priority_queue >, std::less >", + "staticSize": 32, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' + [cases.adapter_deque_empty] + param_types = ["const std::priority_queue>&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "priority_queue >, std::less >", + "staticSize": 88, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.adapter_deque_some] + param_types = ["const std::priority_queue>&"] + setup = "return std::priority_queue>({}, {3,2,1});" + expect_json = '''[{ + "typeName": "priority_queue >, std::less >", + "staticSize": 88, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' diff --git a/test/integration/std_queue.toml b/test/integration/std_queue.toml new file mode 100644 index 0000000..362d0e6 --- /dev/null +++ b/test/integration/std_queue.toml @@ -0,0 +1,108 @@ +includes = ["queue"] +[cases] + [cases.int_empty] + param_types = ["const std::queue&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "queue > >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.int_some] + param_types = ["const std::queue&"] + setup = "return std::queue({1,2,3});" + expect_json = '''[{ + "typeName": "queue > >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' + [cases.queue_int_empty] + param_types = ["const std::queue>&"] + setup = "return {};" + expect_json = '''[{ + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 80, + "members": [ + { + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 80 + } + ]}]''' + [cases.queue_int_some] + param_types = ["const std::queue>&"] + setup = ''' + return std::queue>({ + std::queue({1,2,3}), + std::queue(), + std::queue({4,5}) + }); + ''' + expect_json = '''[{ + "staticSize": 80, "dynamicSize": 260, "length": 3, "capacity": 3, "elementStaticSize": 80, + "members": [ + { + "staticSize": 80, "dynamicSize": 260, "length": 3, "capacity": 3, "elementStaticSize": 80, + "members": [ + { + "typeName": "queue > >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ] + }, + { + "typeName": "queue > >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ] + }, + { + "typeName": "queue > >", + "staticSize": 80, "dynamicSize": 8, "length": 2, "capacity": 2, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 8, "length": 2, "capacity": 2, "elementStaticSize": 4 + } + ] + } + ] + } + ]}]''' + [cases.adapter_vector_empty] + param_types = ["const std::queue>&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "queue > >", + "staticSize": 24, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.adapter_vector_some] + param_types = ["const std::queue>&"] + setup = "return std::queue>({1,2,3});" + expect_json = '''[{ + "typeName": "queue > >", + "staticSize": 24, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' diff --git a/test/integration/std_reference_wrapper.toml b/test/integration/std_reference_wrapper.toml new file mode 100644 index 0000000..601e3c8 --- /dev/null +++ b/test/integration/std_reference_wrapper.toml @@ -0,0 +1,20 @@ +includes = ["functional"] +[cases] + [cases.int] + param_types = ["const std::reference_wrapper&"] + setup = "return std::ref(*new int(1));" + expect_json = '[{"staticSize":8, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4}]' + [cases.vector] + param_types = ["const std::vector>&"] + setup = "return {{std::ref(*new int(1)), std::ref(*new int(2)), std::ref(*new int(3))}};" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":36, + "length":3, + "capacity":3, + "elementStaticSize":8, + "members":[ + {"staticSize":8, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4}, + {"staticSize":8, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4}, + {"staticSize":8, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4} + ]}]''' diff --git a/test/integration/std_set_custom_comparator.toml b/test/integration/std_set_custom_comparator.toml new file mode 100644 index 0000000..60b4c19 --- /dev/null +++ b/test/integration/std_set_custom_comparator.toml @@ -0,0 +1,64 @@ +definitions = ''' + struct CustomComparator { + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + struct SmallSizedCustomComparator { + double a; + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + struct BigSizedCustomComparator { + double d[1000]; + bool operator()(const int& left, const int& right) const { + return left < right; + } + }; + + struct Foo { + std::set m1; + std::set m2; + std::set m3; + std::set m4; + }; + +''' +includes = ["set", "functional"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + + for (int i = 0; i < 3; i++) { + foo.m1.insert(i); + } + + for (int i = 0; i < 5; i++) { + foo.m2.insert(i); + } + + for (int i = 0; i < 7; i++) { + foo.m3.insert(i); + } + + for (int i = 0; i < 9; i++) { + foo.m4.insert(i); + } + + return {foo}; + ''' + expect_json = '''[{ + "staticSize":8184, + "dynamicSize":288, + "members":[ + {"name":"m1", "staticSize":48, "dynamicSize":36, "length":3, "capacity":3, "elementStaticSize":12}, + {"name":"m2", "staticSize":48, "dynamicSize":60, "length":5, "capacity":5, "elementStaticSize":12}, + {"name":"m3", "staticSize":48, "dynamicSize":84, "length":7, "capacity":7, "elementStaticSize":12}, + {"name":"m4", "staticSize":8040, "dynamicSize":108, "length":9, "capacity":9, "elementStaticSize":12} + ]}]''' diff --git a/test/integration/std_smart_ptr.toml b/test/integration/std_smart_ptr.toml new file mode 100644 index 0000000..030ba79 --- /dev/null +++ b/test/integration/std_smart_ptr.toml @@ -0,0 +1,178 @@ +includes = ["memory", "cstdint"] +definitions = ''' + void void_int_deleter(void *void_ptr) { + auto *int_ptr = static_cast(void_ptr); + delete int_ptr; + } +''' +[cases] + [cases.unique_ptr_uint64_empty] + param_types = ["std::unique_ptr&"] + setup = "return {nullptr};" + expect_json = ''' + [ + { + "staticSize": 8, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.unique_ptr_uint64_present] + param_types = ["std::unique_ptr&"] + setup = "return {std::make_unique(64)};" + expect_json = ''' + [ + { + "staticSize": 8, + "dynamicSize": 8, + "length": 1, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.unique_ptr_vector_empty] + param_types = ["std::unique_ptr>&"] + setup = "return {nullptr};" + expect_json = ''' + [ + { + "staticSize": 8, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 24 + } + ] + ''' + [cases.unique_ptr_vector_present] + param_types = ["std::unique_ptr>&"] + setup = "return {std::make_unique>(std::initializer_list({1,2,3,4,5}))};" + expect_json = ''' + [ + { + "staticSize": 8, + "dynamicSize": 64, + "length": 1, + "capacity": 1, + "elementStaticSize": 24, + "members": [ + { + "staticSize": 24, + "dynamicSize": 40 + } + ] + } + ] + ''' + [cases.unique_ptr_void_empty] + param_types = ["std::unique_ptr&"] + setup = "return {std::unique_ptr(nullptr, &void_int_deleter)};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0 + } + ] + ''' + [cases.unique_ptr_void_present] + skip = "we don't report the dynamic size" + param_types = ["std::unique_ptr&"] + setup = "return {std::unique_ptr(new int, &void_int_deleter)};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 8 + } + ] + ''' + [cases.shared_ptr_uint64_empty] + param_types = ["std::shared_ptr&"] + setup = "return {nullptr};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.shared_ptr_uint64_present] + param_types = ["std::shared_ptr&"] + setup = "return std::make_shared(64);" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 8, + "length": 1, + "capacity": 1, + "elementStaticSize": 8 + } + ] + ''' + [cases.shared_ptr_vector_empty] + param_types = ["std::shared_ptr>&"] + setup = "return {nullptr};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0, + "length": 0, + "capacity": 1, + "elementStaticSize": 24 + } + ] + ''' + [cases.shared_ptr_vector_present] + param_types = ["std::shared_ptr>&"] + setup = "return std::make_shared>(std::initializer_list({1,2,3,4,5}));" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 64, + "length": 1, + "capacity": 1, + "elementStaticSize": 24, + "members": [ + { + "staticSize": 24, + "dynamicSize": 40 + } + ] + } + ] + ''' + [cases.shared_ptr_void_empty] + param_types = ["std::shared_ptr&"] + setup = "return {nullptr};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 0 + } + ] + ''' + [cases.shared_ptr_void_present] + skip = "we don't report the dynamic size" + param_types = ["std::shared_ptr&"] + setup = "return {std::shared_ptr(new int)};" + expect_json = ''' + [ + { + "staticSize": 16, + "dynamicSize": 8 + } + ] + ''' diff --git a/test/integration/std_stack.toml b/test/integration/std_stack.toml new file mode 100644 index 0000000..8b01b74 --- /dev/null +++ b/test/integration/std_stack.toml @@ -0,0 +1,108 @@ +includes = ["stack", "vector"] +[cases] + [cases.int_empty] + param_types = ["const std::stack&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "stack > >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.int_some] + param_types = ["const std::stack&"] + setup = "return std::stack({1,2,3});" + expect_json = '''[{ + "typeName": "stack > >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' + [cases.stack_int_empty] + param_types = ["const std::stack>&"] + setup = "return {};" + expect_json = '''[{ + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 80, + "members": [ + { + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 80 + } + ]}]''' + [cases.stack_int_some] + param_types = ["const std::stack>&"] + setup = ''' + return std::stack>({ + std::stack({1,2,3}), + std::stack(), + std::stack({4,5}) + }); + ''' + expect_json = '''[{ + "staticSize": 80, "dynamicSize": 260, "length": 3, "capacity": 3, "elementStaticSize": 80, + "members": [ + { + "staticSize": 80, "dynamicSize": 260, "length": 3, "capacity": 3, "elementStaticSize": 80, + "members": [ + { + "typeName": "stack > >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ] + }, + { + "typeName": "stack > >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ] + }, + { + "typeName": "stack > >", + "staticSize": 80, "dynamicSize": 8, "length": 2, "capacity": 2, "elementStaticSize": 4, + "members": [ + { + "typeName": "deque >", + "staticSize": 80, "dynamicSize": 8, "length": 2, "capacity": 2, "elementStaticSize": 4 + } + ] + } + ] + } + ]}]''' + [cases.adapter_vector_empty] + param_types = ["const std::stack>&"] + setup = "return {};" + expect_json = '''[{ + "typeName": "stack > >", + "staticSize": 24, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 0, "length": 0, "capacity": 0, "elementStaticSize": 4 + } + ]}]''' + [cases.adapter_vector_some] + param_types = ["const std::stack>&"] + setup = "return std::stack>({1,2,3});" + expect_json = '''[{ + "typeName": "stack > >", + "staticSize": 24, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4, + "members": [ + { + "typeName": "vector >", + "staticSize": 24, "dynamicSize": 12, "length": 3, "capacity": 3, "elementStaticSize": 4 + } + ]}]''' diff --git a/test/integration/std_string.toml b/test/integration/std_string.toml new file mode 100644 index 0000000..fe6835d --- /dev/null +++ b/test/integration/std_string.toml @@ -0,0 +1,68 @@ +includes = ["string"] +[cases] + [cases.empty] + param_types = ["std::string&"] + setup = "return {};" + expect_json = ''' + [ + { + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 0, + "members": [ + { + "staticSize": 32, + "dynamicSize": 0, + "length": 0, + "capacity": 15, + "elementStaticSize": 1 + } + ] + } + ] + ''' + [cases.sso] + param_types = ["std::string&"] + setup = 'return {"012345"};' + expect_json = ''' + [ + { + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 0, + "members": [ + { + "staticSize": 32, + "dynamicSize": 0, + "length": 6, + "capacity": 15, + "elementStaticSize": 1 + } + ] + } + ] + ''' + [cases.heap_allocated] + param_types = ["std::string&"] + setup = 'return {"abcdefghijklmnopqrstuvwxzy"};' + expect_json = ''' + [ + { + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 26, + "members": [ + { + "staticSize": 32, + "dynamicSize": 26, + "length": 26, + "capacity": 26, + "elementStaticSize": 1 + } + ] + } + ] + ''' diff --git a/test/integration/std_unordered_map.toml b/test/integration/std_unordered_map.toml new file mode 100644 index 0000000..d4ce073 --- /dev/null +++ b/test/integration/std_unordered_map.toml @@ -0,0 +1,8 @@ +includes = ["unordered_map"] +[cases] + [cases.int_int] + skip = true + param_types = ["const std::unordered_map&"] + setup = "return {{{1,2},{3,4}}};" + # TODO confirm this JSON is correct + expect_json = '[{"staticSize":56, "dynamicSize":0, "length":2, "capacity":2, "elementStaticSize":0}]' diff --git a/test/integration/std_unordered_map_custom_operator.toml b/test/integration/std_unordered_map_custom_operator.toml new file mode 100644 index 0000000..44c7934 --- /dev/null +++ b/test/integration/std_unordered_map_custom_operator.toml @@ -0,0 +1,66 @@ +definitions = ''' + + template + class CustomIntHasher + { + double d[N]; + public: + size_t operator() (int const& key) const + { + return std::hash{}(key); + } + }; + + template + class CustomEqualFnInt + { + double d[N]; + public: + bool operator() (int const& t1, int const& t2) const + { + return t1 == t2; + } + }; + + struct Foo { + std::unordered_map m1; + std::unordered_map> m2; + std::unordered_map, CustomEqualFnInt<8>> m3; + std::unordered_map, CustomEqualFnInt<8>> m4; + }; +''' +includes = ["unordered_map"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + + for (int i = 0; i < 3; i++) { + foo.m1[i] = (i * 10); + } + + for (int i = 0; i < 5; i++) { + foo.m2[i] = (i * 10); + } + + for (int i = 0; i < 7; i++) { + foo.m3[i] = (i * 10); + } + + for (int i = 0; i < 9; i++) { + foo.m4[i] = (i * 10); + } + + return {foo}; + ''' + expect_json = '''[{ + "staticSize":480, + "dynamicSize":1184, + "members":[ + {"name":"m1", "staticSize":56, "dynamicSize":200, "length":3, "capacity":3, "elementStaticSize":32}, + {"name":"m2", "staticSize":120, "dynamicSize":264, "length":5, "capacity":5, "elementStaticSize":32}, + {"name":"m3", "staticSize":120, "dynamicSize":328, "length":7, "capacity":7, "elementStaticSize":32}, + {"name":"m4", "staticSize":184, "dynamicSize":392, "length":9, "capacity":9, "elementStaticSize":32} + ]}]''' diff --git a/test/integration/std_unordered_set_custom_operator.toml b/test/integration/std_unordered_set_custom_operator.toml new file mode 100644 index 0000000..b9e3bd9 --- /dev/null +++ b/test/integration/std_unordered_set_custom_operator.toml @@ -0,0 +1,66 @@ +definitions = ''' + + template + class CustomIntHasher + { + double d[N]; + public: + size_t operator() (int const& key) const + { + return std::hash{}(key); + } + }; + + template + class CustomEqualFnInt + { + double d[N]; + public: + bool operator() (int const& t1, int const& t2) const + { + return t1 == t2; + } + }; + + struct Foo { + std::unordered_set m1; + std::unordered_set> m2; + std::unordered_set, CustomEqualFnInt<8>> m3; + std::unordered_set, CustomEqualFnInt<8>> m4; + }; +''' +includes = ["unordered_set"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + + for (int i = 0; i < 3; i++) { + foo.m1.insert(i); + } + + for (int i = 0; i < 5; i++) { + foo.m2.insert(i); + } + + for (int i = 0; i < 7; i++) { + foo.m3.insert(i); + } + + for (int i = 0; i < 9; i++) { + foo.m4.insert(i); + } + + return {foo}; + ''' + expect_json = '''[{ + "staticSize":480, + "dynamicSize":704, + "members":[ + {"name":"m1", "staticSize":56, "dynamicSize":140, "length":3, "capacity":3, "elementStaticSize":12}, + {"name":"m2", "staticSize":120, "dynamicSize":164, "length":5, "capacity":5, "elementStaticSize":12}, + {"name":"m3", "staticSize":120, "dynamicSize":188, "length":7, "capacity":7, "elementStaticSize":12}, + {"name":"m4", "staticSize":184, "dynamicSize":212, "length":9, "capacity":9, "elementStaticSize":12} + ]}]''' diff --git a/test/integration/std_variant.toml b/test/integration/std_variant.toml new file mode 100644 index 0000000..4942a59 --- /dev/null +++ b/test/integration/std_variant.toml @@ -0,0 +1,153 @@ +includes = ["variant", "utility"] +definitions = ''' + struct Thrower { + explicit Thrower(int) {} + Thrower(const Thrower&) { throw std::domain_error("copy ctor"); } + Thrower& operator=(const Thrower&) = default; + }; +''' + +[cases] + [cases.char_int64_1] + param_types = ["const std::variant&"] + setup = "return 'a';" + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":8, + "members":[ + {"typeName":"char", "staticSize":1, "dynamicSize":0} + ]}]''' + [cases.char_int64_2] + param_types = ["const std::variant&"] + setup = "return 1234;" + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":8, + "members":[ + {"typeName":"long", "staticSize":8, "dynamicSize":0} + ]}]''' + + [cases.vector_int_1] + param_types = ["const std::variant, int>&"] + setup = "return std::vector{1,2,3};" + expect_json = '''[{ + "staticSize":32, + "dynamicSize":12, + "length":1, + "capacity":1, + "elementStaticSize":24, + "members":[ + { + "typeName":"vector >", + "staticSize":24, + "dynamicSize":12, + "length":3, + "capacity":3, + "elementStaticSize":4 + } + ]}]''' + [cases.vector_int_2] + param_types = ["const std::variant, int>&"] + setup = "return 123;" + expect_json = '''[{ + "staticSize":32, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":24, + "members":[ + { + "typeName":"int", + "staticSize":4, + "dynamicSize":0 + } + ]}]''' + + [cases.optional] + # This test case ensures that the alignment of std::variant is set + # correctly, as otherwise the size of the std::optional would be wrong + param_types = ["const std::optional>&"] + setup = "return 123;" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":16, + "members":[ + { + "staticSize":16, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":8, + "members":[ + {"typeName":"long", "staticSize":8, "dynamicSize":0} + ] + } + ]}]''' + + [cases.empty] + # https://en.cppreference.com/w/cpp/utility/variant/valueless_by_exception + param_types = ["const std::variant&"] + setup = ''' + std::variant var{123}; + try { + var = Thrower(456); + } + catch (const std::domain_error &ex) { + // Ignore + } + return var; + ''' + expect_json = '''[{ + "staticSize":8, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":4, + "NOT":"members" + }]''' + + # With less than 256 options, parameter indexes are stored in a uint8_t and + # the invalid index value is 0xff. These tests check that we regonise that + # 0xff can be a valid index if there are at least 256 parameters, and that + # the invalid index value is raised to 0xffff. + [cases.256_params_256] + param_types = ["const std::variant&"] + setup = "return 'a';" + expect_json = '''[{ + "staticSize":8, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":4, + "members":[ + {"typeName":"char", "staticSize":1, "dynamicSize":0} + ]}]''' + [cases.256_params_empty] + param_types = ["const std::variant&"] + setup = ''' + std::variant var{'a'}; + try { + var = Thrower(456); + } + catch (const std::domain_error &ex) { + // Ignore + } + return var; + ''' + expect_json = '''[{ + "staticSize":8, + "dynamicSize":0, + "length":1, + "capacity":1, + "elementStaticSize":4, + "NOT":"members" + }]''' diff --git a/test/integration/std_vector.toml b/test/integration/std_vector.toml new file mode 100644 index 0000000..316e2c6 --- /dev/null +++ b/test/integration/std_vector.toml @@ -0,0 +1,46 @@ +includes = ["vector"] +[cases] + [cases.int_empty] + param_types = ["const std::vector&"] + setup = "return {};" + expect_json = '[{"staticSize":24, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":4}]' + [cases.int_some] + param_types = ["const std::vector&"] + setup = "return {{1,2,3}};" + expect_json = '[{"staticSize":24, "dynamicSize":12, "length":3, "capacity":3, "elementStaticSize":4}]' + [cases.bool_empty] + skip = true + param_types = ["const std::vector&"] + setup = "return {};" + expect_json = '[{"staticSize":40, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":0.125}]' + [cases.bool_some] + skip = true + param_types = ["const std::vector&"] + setup = "return {{true, false, true}};" + expect_json = '[{"staticSize":40,"dynamicSize":8, "length":3, "capacity":64, "elementStaticSize":0.125}]' + [cases.vector_int_empty] + param_types = ["const std::vector>&"] + setup = "return {};" + expect_json = '[{"staticSize":24, "dynamicSize":0, "length":0, "capacity":0, "elementStaticSize":24}]' + [cases.vector_int_some] + param_types = ["const std::vector>&"] + setup = "return {{{1,2,3},{4},{5,6}}};" + expect_json = '''[{ + "staticSize":24, + "dynamicSize":96, + "length":3, + "capacity":3, + "elementStaticSize":24, + "members":[ + {"staticSize":24, "dynamicSize":12, "length":3, "capacity":3, "elementStaticSize":4}, + {"staticSize":24, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4}, + {"staticSize":24, "dynamicSize":8, "length":2, "capacity":2, "elementStaticSize":4} + ]}]''' + [cases.reserve] + param_types = ["const std::vector&"] + setup = ''' + std::vector ret = {1,2,3}; + ret.reserve(10); + return ret; + ''' + expect_json = '[{"staticSize":24, "dynamicSize":40, "length":3, "capacity":10, "elementStaticSize":4}]' diff --git a/test/integration/std_vector_del_allocator.toml b/test/integration/std_vector_del_allocator.toml new file mode 100644 index 0000000..475074c --- /dev/null +++ b/test/integration/std_vector_del_allocator.toml @@ -0,0 +1,59 @@ +definitions = ''' + namespace nsA { + struct C { + int a; + }; + } + namespace nsB { + struct C { + int b; + }; + } + + template + class CustomAllocator: public std::allocator + { + + }; + + // Naming conflict will only work if OI deletes the allocator field for vector + struct Foo { + std::vector> v1; + std::vector> v2; + }; + +''' +includes = ["vector"] + +[cases] + [cases.a] + param_types = ["const Foo&"] + setup = ''' + Foo foo; + foo.v1.resize(1); + foo.v2.resize(2); + return {foo}; + ''' + expect_json = '''[{ + "staticSize":48, + "dynamicSize":12, + "members":[ + {"name":"v1", "staticSize":24, "dynamicSize":4, "length":1, "capacity":1, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"a", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]}, + {"name":"v2", "staticSize":24, "dynamicSize":8, "length":2, "capacity":2, "elementStaticSize":4, + "members":[ + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]}, + {"name":"", "typeName": "C", "staticSize":4, "dynamicSize":0, + "members":[ + {"name":"b", "typeName": "int", "staticSize":4, "dynamicSize":0} + ]} + ]} + ]}]''' diff --git a/test/integration/thrift_isset.toml b/test/integration/thrift_isset.toml new file mode 100644 index 0000000..ee9d21a --- /dev/null +++ b/test/integration/thrift_isset.toml @@ -0,0 +1,227 @@ +thrift_definitions = ''' + include "thrift/annotation/cpp.thrift" + include "thrift/annotation/thrift.thrift" + + struct MyThriftStructUnpacked { + 1: optional i32 a; + 2: optional i32 b; + 3: optional i32 c; + } + + @cpp.PackIsset + struct MyThriftStructPacked { + 1: optional i32 a; + 2: optional i32 b; + 3: optional i32 c; + 4: optional i32 d; + 5: optional i32 e; + 6: optional i32 f; + 7: optional i32 g; + 8: optional i32 h; + 9: optional i32 i; + 10: optional i32 j; + } + + @cpp.PackIsset{atomic = false} + struct MyThriftStructPackedNonAtomic { + 1: optional i32 a; + 2: optional i32 b; + 3: optional i32 c; + 4: optional i32 d; + } + + struct MyThriftStructOutOfOrder { + 3: i32 a; + 1: i32 b; + 2: i32 c; + } + + struct MyThriftStructRequired { + 1: required i32 a; + 2: optional i32 b; + 3: optional i32 c; + 4: required i32 d; + 5: required i32 e; + 6: optional i32 f; + } + + struct MyThriftStructBoxed { + @cpp.Ref{type = cpp.RefType.Unique} + 1: optional i32 a; + @thrift.Box + 2: optional i32 b; + 3: optional i32 c; + 4: optional i32 d; + 5: optional i32 e; + } +''' +raw_definitions = ''' +namespace cpp2 { + MyThriftStructBoxed::MyThriftStructBoxed() : + __fbthrift_field_b(), + __fbthrift_field_c(), + __fbthrift_field_d(), + __fbthrift_field_e() { + } + MyThriftStructBoxed::~MyThriftStructBoxed() {} + MyThriftStructBoxed::MyThriftStructBoxed(::cpp2::MyThriftStructBoxed&& other) noexcept : + __fbthrift_field_a(std::move(other.__fbthrift_field_a)), + __fbthrift_field_b(std::move(other.__fbthrift_field_b)), + __fbthrift_field_c(std::move(other.__fbthrift_field_c)), + __fbthrift_field_d(std::move(other.__fbthrift_field_d)), + __fbthrift_field_e(std::move(other.__fbthrift_field_e)), + __isset(other.__isset) { + } +} // namespace cpp2 +''' + +[cases] + [cases.unpacked] + param_types = ["const cpp2::MyThriftStructUnpacked&"] + setup = ''' + cpp2::MyThriftStructUnpacked ret; + ret.a_ref() = 1; + ret.c_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":3} + ]}]''' + + [cases.packed] + param_types = ["const cpp2::MyThriftStructPacked&"] + setup = ''' + cpp2::MyThriftStructPacked ret; + ret.a_ref() = 1; + ret.c_ref() = 1; + ret.d_ref() = 1; + ret.g_ref() = 1; + ret.h_ref() = 1; + ret.j_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":44, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_d", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_e", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_f", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_g", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_h", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_i", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_j", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":2} + ]}]''' + + [cases.packed_non_atomic] + param_types = ["const cpp2::MyThriftStructPackedNonAtomic&"] + setup = ''' + cpp2::MyThriftStructPackedNonAtomic ret; + ret.a_ref() = 1; + ret.c_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":20, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_d", "staticSize":4, "isset":false}, + {"name":"__isset", "staticSize":1} + ]}]''' + + [cases.out_of_order] + param_types = ["const cpp2::MyThriftStructOutOfOrder&"] + setup = ''' + cpp2::MyThriftStructOutOfOrder ret; + ret.b_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":false}, + {"name":"__isset", "staticSize":3} + ]}]''' + + [cases.required] + param_types = ["const cpp2::MyThriftStructRequired&"] + setup = ''' + cpp2::MyThriftStructRequired ret; + ret.b_ref() = 1; + ret.f_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":28, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_d", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_e", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_f", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":3} + ]}]''' + + [cases.box] + param_types = ["const cpp2::MyThriftStructBoxed&"] + setup = ''' + cpp2::MyThriftStructBoxed ret; + ret.d_ref() = 1; + ret.e_ref() = 1; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":32, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":8, "NOT":"isset"}, + {"name":"__fbthrift_field_b", "staticSize":8, "NOT":"isset"}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_d", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_e", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":3} + ]}]''' + + [cases.no_capture] + param_types = ["const cpp2::MyThriftStructBoxed&"] + setup = ''' + cpp2::MyThriftStructBoxed ret; + ret.d_ref() = 1; + ret.e_ref() = 1; + return ret; + ''' + expect_json = '''[{ + "staticSize":32, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":8, "NOT":"isset"}, + {"name":"__fbthrift_field_b", "staticSize":8, "NOT":"isset"}, + {"name":"__fbthrift_field_c", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_d", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_e", "staticSize":4, "NOT":"isset"}, + {"name":"__isset", "staticSize":3} + ]}]''' diff --git a/test/integration/thrift_isset_missing.toml b/test/integration/thrift_isset_missing.toml new file mode 100644 index 0000000..7fab4ca --- /dev/null +++ b/test/integration/thrift_isset_missing.toml @@ -0,0 +1,142 @@ +# This file contains tests for capturing of Thrift's isset data when the +# TStructDataStorage::isset_indexes symbol is not present for every type. +# +# It is very dependent on the inner workings of CodeGen and Thrift so may be +# brittle. +# +# The test case "present" does not do anything special and so acts as a canary +# to determine whether other test failures are legitimate or due to some +# internal Thrift details changing. + +includes = ["thrift/lib/cpp2/gen/module_types_h.h"] +thrift_definitions = "" +raw_definitions = ''' + namespace ns_thrift_isset_missing { + class FakeThriftWithoutData final { + public: + FakeThriftWithoutData() : + __fbthrift_field_a(), + __fbthrift_field_b(), + __fbthrift_field_c() { + } + ::std::int32_t __fbthrift_field_a; + ::std::int32_t __fbthrift_field_b; + ::std::int32_t __fbthrift_field_c; + ::apache::thrift::detail::isset_bitset<3, apache::thrift::detail::IssetBitsetOption::Unpacked> __isset; + }; + class FakeThriftWithData final { + public: + FakeThriftWithData() : + __fbthrift_field_a(), + __fbthrift_field_b(), + __fbthrift_field_c() { + } + ::std::int32_t __fbthrift_field_a; + ::std::int32_t __fbthrift_field_b; + ::std::int32_t __fbthrift_field_c; + ::apache::thrift::detail::isset_bitset<3, apache::thrift::detail::IssetBitsetOption::Unpacked> __isset; + }; + + struct Mixed { + FakeThriftWithData with_data; + FakeThriftWithoutData without_data; + }; + } // namespace ns_thrift_isset_missing + + namespace apache { namespace thrift { + template <> struct TStructDataStorage<::ns_thrift_isset_missing::FakeThriftWithData> { + static constexpr const std::size_t fields_size = 3; + private: + static const std::array isset_indexes; + }; + const std::array TStructDataStorage<::ns_thrift_isset_missing::FakeThriftWithData>::isset_indexes = {{ + 0, + 1, + 2, + }}; + }} // apache::thrift +''' +[cases] + [cases.present] + param_types = ["const FakeThriftWithData&"] + setup = ''' + FakeThriftWithData ret; + ret.__fbthrift_field_a = 1; + ret.__isset.at(0) = true; + ret.__fbthrift_field_c = 1; + ret.__isset.at(2) = true; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":3} + ]}]''' + [cases.missing] + param_types = ["const FakeThriftWithoutData&"] + setup = ''' + FakeThriftWithoutData ret; + ret.__fbthrift_field_a = 1; + ret.__isset.at(0) = true; + ret.__fbthrift_field_c = 1; + ret.__isset.at(2) = true; + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_b", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_c", "staticSize":4, "NOT":"isset"}, + {"name":"__isset", "staticSize":3} + ]}]''' + [cases.mixed] + param_types = ["const Mixed&"] + setup = ''' + Mixed ret; + + ret.with_data.__fbthrift_field_a = 1; + ret.with_data.__isset.at(0) = true; + ret.with_data.__fbthrift_field_c = 1; + ret.with_data.__isset.at(2) = true; + + ret.without_data.__fbthrift_field_a = 1; + ret.without_data.__isset.at(0) = true; + ret.without_data.__fbthrift_field_c = 1; + ret.without_data.__isset.at(2) = true; + + return ret; + ''' + cli_options = ["--capture-thrift-isset"] + expect_json = '''[{ + "staticSize":32, + "dynamicSize":0, + "members":[ + { + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "isset":true}, + {"name":"__fbthrift_field_b", "staticSize":4, "isset":false}, + {"name":"__fbthrift_field_c", "staticSize":4, "isset":true}, + {"name":"__isset", "staticSize":3} + ] + }, + { + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_b", "staticSize":4, "NOT":"isset"}, + {"name":"__fbthrift_field_c", "staticSize":4, "NOT":"isset"}, + {"name":"__isset", "staticSize":3} + ] + } + ]}]''' diff --git a/test/integration/thrift_namespaces.toml b/test/integration/thrift_namespaces.toml new file mode 100644 index 0000000..b99aceb --- /dev/null +++ b/test/integration/thrift_namespaces.toml @@ -0,0 +1,26 @@ +thrift_definitions = ''' + namespace cpp2 namespaceA.namespaceB + + struct TTTTT { + 1: i32 a; + 2: i32 b; + 3: i32 c; + } +''' + +[cases] + [cases.a] + param_types = ["const namespaceA::namespaceB::TTTTT&"] + setup = ''' + namespaceA::namespaceB::TTTTT ret; + return ret; + ''' + expect_json = '''[{ + "staticSize":16, + "dynamicSize":0, + "members":[ + {"name":"__fbthrift_field_a", "staticSize":4}, + {"name":"__fbthrift_field_b", "staticSize":4}, + {"name":"__fbthrift_field_c", "staticSize":4}, + {"name":"__isset", "staticSize":3} + ]}]''' diff --git a/test/integration/typedefed_parent.toml b/test/integration/typedefed_parent.toml new file mode 100644 index 0000000..05e7a9e --- /dev/null +++ b/test/integration/typedefed_parent.toml @@ -0,0 +1,42 @@ +definitions = ''' + struct Foo { + std::vector a = {10, 20, 30}; + }; + typedef Foo Parent; + struct Bar: Parent { + std::vector b = {1, 2, 3, 4, 5}; + }; + + typedef Parent Parent_2; + struct Bar_2: Parent_2 { + std::vector c = {1, 2, 3, 4, 5}; + }; +''' + +[cases] + [cases.simple_typedef_parent] + param_types = ["const Bar&"] + setup = ''' + Bar bar; + return {bar}; + ''' + expect_json = '''[{ + "staticSize":48, + "dynamicSize":32, + "members":[ + {"name":"a", "staticSize":24, "dynamicSize":12, "length":3, "capacity":3}, + {"name":"b", "staticSize":24, "dynamicSize":20, "length":5, "capacity":5} + ]}]''' + [cases.multilevel_typedef_parent] + param_types = ["const Bar_2&"] + setup = ''' + Bar_2 bar; + return {bar}; + ''' + expect_json = '''[{ + "staticSize":48, + "dynamicSize":32, + "members":[ + {"name":"a", "staticSize":24, "dynamicSize":12, "length":3, "capacity":3}, + {"name":"c", "staticSize":24, "dynamicSize":20, "length":5, "capacity":5} + ]}]''' diff --git a/test/integration_cycles.cpp b/test/integration_cycles.cpp new file mode 100644 index 0000000..302ab61 --- /dev/null +++ b/test/integration_cycles.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include +#include + +struct RawNode { + uint64_t value; + struct RawNode* next; +}; + +void __attribute__((noinline)) rawPointerCycle(struct RawNode* node) { + for (int i = 0; node && i < 10; i++) { + node->value++; + node = node->next; + } +} + +struct UniqueNode { + uint64_t value; + std::unique_ptr next; +}; + +void __attribute__((noinline)) uniquePointerCycle(struct UniqueNode* node) { + for (int i = 0; node && i < 10; i++) { + node->value++; + node = node->next.get(); + } +} + +struct SharedNode { + uint64_t value; + std::shared_ptr next; +}; + +void __attribute__((noinline)) sharedPointerCycle(struct SharedNode* node) { + for (int i = 0; node && i < 10; i++) { + node->value++; + node = node->next.get(); + } +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, + "One of 'raw', 'unique', or 'shared' should be provided as an " + "argument\n"); + return EXIT_FAILURE; + } + if (strcmp(argv[1], "raw") == 0) { + RawNode third{2, nullptr}, second{1, &third}, first{0, &second}; + third.next = &first; + for (int i = 0; i < 1000; i++) { + rawPointerCycle(&first); + sleep(1); + } + } else if (strcmp(argv[1], "unique") == 0) { + std::unique_ptr first = std::make_unique(); + UniqueNode* firstPtr = first.get(); + first->next = std::make_unique(); + first->next->next = std::make_unique(); + first->next->next->next = std::move(first); + for (int i = 0; i < 1000; i++) { + uniquePointerCycle(firstPtr); + sleep(1); + } + } else if (strcmp(argv[1], "shared") == 0) { + std::shared_ptr first = std::make_shared(); + SharedNode* firstPtr = first.get(); + first->next = std::make_shared(); + first->next->next = std::make_shared(); + first->next->next->next = first; + for (int i = 0; i < 1000; i++) { + sharedPointerCycle(firstPtr); + sleep(1); + } + } else { + fprintf(stderr, + "One of 'raw', 'unique', or 'shared' should be provided as an " + "argument\n"); + return EXIT_FAILURE; + } +} diff --git a/test/integration_cycles_raw.oid b/test/integration_cycles_raw.oid new file mode 100644 index 0000000..254e45e --- /dev/null +++ b/test/integration_cycles_raw.oid @@ -0,0 +1 @@ +entry:_Z15rawPointerCycleP7RawNode:arg0 diff --git a/test/integration_cycles_shared.oid b/test/integration_cycles_shared.oid new file mode 100644 index 0000000..dd8a75b --- /dev/null +++ b/test/integration_cycles_shared.oid @@ -0,0 +1 @@ +entry:_Z18sharedPointerCycleP10SharedNode:arg0 diff --git a/test/integration_cycles_unique.oid b/test/integration_cycles_unique.oid new file mode 100644 index 0000000..026b9cb --- /dev/null +++ b/test/integration_cycles_unique.oid @@ -0,0 +1 @@ +entry:_Z18uniquePointerCycleP10UniqueNode:arg0 diff --git a/test/integration_entry_badFunc_arg0.oid b/test/integration_entry_badFunc_arg0.oid new file mode 100644 index 0000000..da861e4 --- /dev/null +++ b/test/integration_entry_badFunc_arg0.oid @@ -0,0 +1 @@ +entry:badFunc:arg0 diff --git a/test/integration_entry_doStuff_arg0.oid b/test/integration_entry_doStuff_arg0.oid new file mode 100644 index 0000000..0e3ea0c --- /dev/null +++ b/test/integration_entry_doStuff_arg0.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0 diff --git a/test/integration_entry_doStuff_arg0_arg1.oid b/test/integration_entry_doStuff_arg0_arg1.oid new file mode 100644 index 0000000..e77f350 --- /dev/null +++ b/test/integration_entry_doStuff_arg0_arg1.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0,arg1 diff --git a/test/integration_entry_doStuff_this.oid b/test/integration_entry_doStuff_this.oid new file mode 100644 index 0000000..514b947 --- /dev/null +++ b/test/integration_entry_doStuff_this.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:this diff --git a/test/integration_entry_incVectSize_arg0.oid b/test/integration_entry_incVectSize_arg0.oid new file mode 100644 index 0000000..b5a6911 --- /dev/null +++ b/test/integration_entry_incVectSize_arg0.oid @@ -0,0 +1 @@ +entry:_ZN3Foo11incVectSizeESt6vectorIiSaIiEE:arg0 diff --git a/test/integration_entry_inc_arg0.oid b/test/integration_entry_inc_arg0.oid new file mode 100644 index 0000000..29ca3ba --- /dev/null +++ b/test/integration_entry_inc_arg0.oid @@ -0,0 +1 @@ +entry:_ZN3Foo3incEv:arg0 diff --git a/test/integration_entry_inc_this.oid b/test/integration_entry_inc_this.oid new file mode 100644 index 0000000..87cd523 --- /dev/null +++ b/test/integration_entry_inc_this.oid @@ -0,0 +1 @@ +entry:_ZN3Foo3incEv:this diff --git a/test/integration_global_myGlobalFoo.oid b/test/integration_global_myGlobalFoo.oid new file mode 100644 index 0000000..19fb215 --- /dev/null +++ b/test/integration_global_myGlobalFoo.oid @@ -0,0 +1 @@ +global:myGlobalFoo diff --git a/test/integration_mt.cpp b/test/integration_mt.cpp new file mode 100644 index 0000000..0359244 --- /dev/null +++ b/test/integration_mt.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +struct Foo { + std::vector>> xs; +}; + +Foo& doStuff(Foo& f) { + std::vector> xs = {{5, 6, 7, 8}}; + f.xs.emplace_back(std::move(xs)); + return f; +} + +void run(long iteration_count) { + for (long i = 0; i < iteration_count; ++i) { + std::chrono::microseconds sleep_time{std::rand() & 0x7f}; + std::this_thread::sleep_for(sleep_time); + + Foo f{{ + { + {1, 2, 3}, + {4, 5, 6}, + }, + }}; + + doStuff(f); + } +} + +void usage(char* prog, int exit_status) { + std::printf("usage: %s \n", prog); + exit(exit_status); +} + +int main(int argc, char* argv[]) { + if (argc != 3) + usage(argv[0], EXIT_FAILURE); + + long iteration_count = atol(argv[1]); + if (iteration_count <= 0) + usage(argv[0], EXIT_FAILURE); + + int thread_count = atoi(argv[2]); + if (thread_count <= 0) + usage(argv[0], EXIT_FAILURE); + + std::srand(std::time(nullptr)); + + std::vector threads; + threads.reserve(thread_count); + for (int i = 0; i < thread_count; ++i) + threads.emplace_back(run, iteration_count); + + for (auto& t : threads) + t.join(); + + return EXIT_SUCCESS; +} diff --git a/test/integration_mt_entry_doStuff_arg0.oid b/test/integration_mt_entry_doStuff_arg0.oid new file mode 100644 index 0000000..6821168 --- /dev/null +++ b/test/integration_mt_entry_doStuff_arg0.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3Foo:arg0 diff --git a/test/integration_mttest.cpp b/test/integration_mttest.cpp new file mode 100644 index 0000000..38466d8 --- /dev/null +++ b/test/integration_mttest.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mttest.h" +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +typedef union { + int i; + float f; + char c; + struct { + int *myArray[3]; + float myArray2[2][2][3]; + } myStruct; + double array[10][20]; +} UN; +typedef UN UN2; +typedef struct { + struct { + union { + int a[10]; + double b; + } b; + int c; + } d; + int i; + float f; + char c; +} ST; +typedef ST ST2; + +/*class Bar { + std::string bar; +};*/ + +using Deleter1 = void (*)(int *); +using Deleter2 = std::function; + +int *create() { + int *i = new int; + *i = 10; + return i; +} +void destroy(int *i) { + delete i; +} + +struct Destroyer { + void operator()(int *i) const { + delete i; + } +}; + +// POD - very important for testing tail padding +class FooParent { + double doubleMember; + bool boolMember; +}; + +// non-POD - very important for testing tail padding +struct BarParent { + protected: + double d; + + public: + int i; +}; + +struct Bar : BarParent { + float f; +}; + +class Foo : FooParent { + public: + bool aBool; + short aShort; + std::vector vectorOfStr; + std::multimap intToStrMultiMap; + std::unique_ptr uniquePointer0; + std::unique_ptr uniquePointer1; + std::unique_ptr uniquePointer2; + Bar myBar; + + std::map> map; + std::unordered_map + unorderedMap; + /* std::vector> mapOfStr; + bool myb; + std::vector> vectorOfPair; */ + UN unionVar; + /*UN2 unionVar2; + ST structVar; + ST2 structVar2; + char t; */ + ST2 structVar2; + char arr[10]; + int aa; + int bb : 1; + int : 0; + int cc : 5; + int dd : 30; + + // Bar bar_arr[5]; + int ref; + std::function testFunc; + std::deque testDeque; + folly::Function testFuncFolly; + + std::queue> testQueue; + float zero[0]; + + __attribute__((noinline)) void inc() { + ref++; + } + __attribute__((noinline)) void incN(int n) { + ref += n; + } + __attribute__((noinline)) void incVectSize(const std::vector vect) { + ref += vect.size(); + } + + Foo() + : uniquePointer1(create(), destroy), + uniquePointer2(create(), Destroyer()) { + } +}; + +Foo myGlobalFoo; +// std::unique_ptr myGlobalFoo; + +// pass in the loop counter in the args +std::vector doStuff(Foo &foo, + std::vector> &m, + std::vector &f, + std::vector> &p) { + std::vector altvect = {1, 3, 5, 7}; + foo.inc(); + foo.incN(altvect.size()); + foo.incVectSize(altvect); + + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + std::cout << " addr of m = " << reinterpret_cast(&m) << std::endl; + std::cout << " addr of p = " << reinterpret_cast(&p) << std::endl; + std::cout << " addr of myGlobalFoo = " + << reinterpret_cast(&myGlobalFoo) << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + /* + * Insert another `ret` instruction that is never executed. + * This is to test that `oid` cleans up all return TRAPS, + * even if the target program has never executed them. + */ + volatile bool dontExecuteButDontOptimizeAway = false; + if (dontExecuteButDontOptimizeAway) + asm("ret"); + + return newvect; +} + +void doStuff(std::vector &f, int i) { + f.push_back(i); + std::cout << "Entries in f: " << f.size() << std::endl; +} + +void doNothing() { + std::cout << "I do nothing, the function does nothing" << std::endl; +} + +void *doit(void *arg) { + doNothing(); + int *loopcnt = reinterpret_cast(arg); + std::vector f; + f.reserve(200); + std::vector> mv; + + std::unordered_map m; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = "ba"; + m["a"] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbaaaaaaaaaaa"] = "bbb"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccaaaaaaaa"] = "bbbb"; + + mv.push_back(m); + mv.push_back(m); + mv.push_back(m); + + std::vector> pv; + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdefghi", 10); + pv.push_back(p); + } + + for (int i = 0; i < *loopcnt; ++i) { + for (int j = 0; j < 3; j++) { + f.push_back("abcdefghijklmn"); + } + for (int j = 0; j < 3; j++) { + f.push_back( + "abcdefghijklmnoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + Foo foo; + + std::multimap mm; + + mm.insert(std::pair(0, 0)); + mm.insert(std::pair(1, 10)); + mm.insert(std::pair(2, 20)); + mm.insert(std::pair(3, 30)); + + mm.insert(std::pair(1, 100)); + + foo.intToStrMultiMap = mm; + + /*foo.vectorOfStr = f; + foo.mapOfStr = mv; + foo.vectorOfPair = pv;*/ + /*foo.bar_arr[0].bar = ""; + foo.bar_arr[1].bar = "0123456789"; + foo.bar_arr[2].bar = "01234567890123456789"; + foo.bar_arr[3].bar = "0123456789012345678901234567890123456789"; + foo.bar_arr[4].bar = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789";*/ + + foo.testFunc = [](int n) { std::cout << n << std::endl; }; + foo.testFuncFolly = [](int n) { std::cout << n << std::endl; }; + foo.testQueue.push(1); + foo.testQueue.push(2); + foo.testQueue.push(3); + + foo.testDeque.push_back(5); + + std::vector> dummy; + std::vector g = doStuff(foo, dummy, f, pv); + doStuff(g, i); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/integration_packed.cpp b/test/integration_packed.cpp new file mode 100644 index 0000000..b2057c9 --- /dev/null +++ b/test/integration_packed.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +struct __attribute__((__packed__)) Foo { + char *p; /* 8 bytes */ + char c; /* 1 byte */ + long x; /* 8 bytes */ +}; + +// struct Foo { +// char *p; /* 8 bytes */ +// char c; /* 8 byte */ +// long x; /* 8 bytes */ +// }; + +Foo myGlobalFoo; + +// pass in the loop counter in the args +int doStuff(Foo &foo) { + return foo.x; +} + +void *doit(void *arg) { + int *loopcnt = reinterpret_cast(arg); + + for (int i = 0; i < *loopcnt; ++i) { + Foo foo; + foo.x = *loopcnt; + + int result = doStuff(foo); + + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + } + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/integration_packed_arg0.oid b/test/integration_packed_arg0.oid new file mode 100644 index 0000000..6821168 --- /dev/null +++ b/test/integration_packed_arg0.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3Foo:arg0 diff --git a/test/integration_return_doStuff_arg0.oid b/test/integration_return_doStuff_arg0.oid new file mode 100644 index 0000000..dc1d792 --- /dev/null +++ b/test/integration_return_doStuff_arg0.oid @@ -0,0 +1 @@ +return:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0 diff --git a/test/integration_return_doStuff_retval.oid b/test/integration_return_doStuff_retval.oid new file mode 100644 index 0000000..9812fe6 --- /dev/null +++ b/test/integration_return_doStuff_retval.oid @@ -0,0 +1 @@ +return:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:retval diff --git a/test/integration_return_incN_arg0.oid b/test/integration_return_incN_arg0.oid new file mode 100644 index 0000000..3fdd569 --- /dev/null +++ b/test/integration_return_incN_arg0.oid @@ -0,0 +1 @@ +return:_ZN3Foo4incNEi:arg0 diff --git a/test/integration_return_inc_this.oid b/test/integration_return_inc_this.oid new file mode 100644 index 0000000..eb0276e --- /dev/null +++ b/test/integration_return_inc_this.oid @@ -0,0 +1 @@ +return:_ZN3Foo3incEv:this diff --git a/test/integration_sleepy.cpp b/test/integration_sleepy.cpp new file mode 100644 index 0000000..82315b4 --- /dev/null +++ b/test/integration_sleepy.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +typedef union { + int i; + float f; + char c; +} UN; +typedef UN UN2; +typedef struct { + int i; + float f; + char c; +} ST; +typedef ST ST2; + +/*class Bar { + std::string bar; +};*/ + +class Foo { + public: + std::vector vectorOfStr; + std::multimap intToStrMultiMap; + /* std::vector> mapOfStr; + bool myb; + std::vector> vectorOfPair; */ + /*UN unionVar; + UN2 unionVar2; + ST structVar; + ST2 structVar2; + char t; */ + char arr[10]; + int aa; + int bb : 1; + int : 0; + int cc : 5; + int dd : 30; + + // Bar bar_arr[5]; + int ref; + std::function testFunc; + std::deque testDeque; + folly::Function testFuncFolly; + + std::queue> testQueue; + + __attribute__((noinline)) void inc() { + ref++; + } + __attribute__((noinline)) void incN(int n) { + ref += n; + } + __attribute__((noinline)) void incVectSize(const std::vector vect) { + ref += vect.size(); + } +}; + +Foo myGlobalFoo; +// std::unique_ptr myGlobalFoo; + +// pass in the loop counter in the args +std::vector doStuff(Foo &foo, + std::vector> &m, + std::vector &f, + std::vector> &p) { + std::vector altvect = {1, 3, 5, 7}; + foo.inc(); + foo.incVectSize(altvect); + myGlobalFoo.vectorOfStr.push_back(std::string("Test String")); + + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + std::cout << " addr of m = " << reinterpret_cast(&m) << std::endl; + std::cout << " addr of p = " << reinterpret_cast(&p) << std::endl; + std::cout << " addr of myGlobalFoo = " + << reinterpret_cast(&myGlobalFoo) << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + return newvect; +} + +void doStuff(std::vector &f, int i) { + f.push_back(i); + std::cout << "Entries in f: " << f.size() << std::endl; +} + +void doNothing() { + std::cout << "I do nothing, the function does nothing" << std::endl; +} + +void *doit(void *arg) { + doNothing(); + int *loopcnt = reinterpret_cast(arg); + std::vector f; + f.reserve(200); + std::vector> mv; + + std::unordered_map m; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = "ba"; + m["a"] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbaaaaaaaaaaa"] = "bbb"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccaaaaaaaa"] = "bbbb"; + + mv.push_back(m); + mv.push_back(m); + mv.push_back(m); + + std::vector> pv; + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdefghi", 10); + pv.push_back(p); + } + + for (int i = 0; i < *loopcnt; ++i) { + for (int j = 0; j < 3; j++) { + f.push_back("abcdefghijklmn"); + } + for (int j = 0; j < 3; j++) { + f.push_back( + "abcdefghijklmnoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + Foo foo; + + std::multimap mm; + + mm.insert(std::pair(0, 0)); + mm.insert(std::pair(1, 10)); + mm.insert(std::pair(2, 20)); + mm.insert(std::pair(3, 30)); + + mm.insert(std::pair(1, 100)); + + foo.intToStrMultiMap = mm; + + /*foo.vectorOfStr = f; + foo.mapOfStr = mv; + foo.vectorOfPair = pv;*/ + /*foo.bar_arr[0].bar = ""; + foo.bar_arr[1].bar = "0123456789"; + foo.bar_arr[2].bar = "01234567890123456789"; + foo.bar_arr[3].bar = "0123456789012345678901234567890123456789"; + foo.bar_arr[4].bar = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789";*/ + + foo.testFunc = [](int n) { std::cout << n << std::endl; }; + foo.testFuncFolly = [](int n) { std::cout << n << std::endl; }; + foo.testQueue.push(1); + foo.testQueue.push(2); + foo.testQueue.push(3); + + foo.testDeque.push_back(5); + + std::vector> dummy; + std::vector g = doStuff(foo, dummy, f, pv); + doStuff(g, i); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(10); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/mapiter.cpp b/test/mapiter.cpp new file mode 100644 index 0000000..bef9185 --- /dev/null +++ b/test/mapiter.cpp @@ -0,0 +1,58 @@ +#include + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + // std::map::iterator it = mapOfWords.begin(); + // std::map::iterator end = mapOfWords.end(); + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + std::cout << "pid == " << getpid() << " (hit RETURN to continue)" + << std::endl; + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + std::cout << "Total size of strings in mapOfWords is " << size << " bytes" + << std::endl; + + return 0; +} diff --git a/test/mttest.h b/test/mttest.h new file mode 100644 index 0000000..4b94759 --- /dev/null +++ b/test/mttest.h @@ -0,0 +1,25 @@ +#include + +struct custom_cmp { + bool operator()(int a, int b) const { + return a < b; + } +}; + +struct OIDTestingTwoString { + std::string first; + std::string second; +}; + +struct customTwoStringEq { + bool operator()(const OIDTestingTwoString &a, const OIDTestingTwoString &b) { + return (a.first == a.first && a.second == b.second); + } +}; + +struct customHash { + std::size_t operator()(const OIDTestingTwoString &two) const { + return ((std::hash()(two.first) ^ + (std::hash()(two.second)))); + } +}; diff --git a/test/mttest1.cpp b/test/mttest1.cpp new file mode 100644 index 0000000..160c5c5 --- /dev/null +++ b/test/mttest1.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +// pass in the loop counter in the args +std::vector doStuff(std::vector &f, int i) { + std::vector altvect = {1, 3, 5, 7}; + + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + + for (int j = 0; j < 40; ++j) { + f.push_back(j); + } + + std::cout << " doStuff entries: " << f.size() << std::endl; + +#if 0 + if (i % 2) + { + std::vector altvect = { 1,3,5,7 }; + return altvect; + /* asm("" : : "a"(altvect)); */ + /* __asm__ volatile ("movl %%rax %0" + ::"m"(altvect):); */ + /* __asm__ volatile ("retq"); */ + } +#endif + + std::cout << syscall(SYS_gettid) << " " << i << "f = " << std::hex << &f + << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + return newvect; +} + +#if 0 +void doStuff(std::vector &f, int i) +{ + f.push_back(i); + + if (i > 1000000) + __asm__ volatile ("retq"); + + std::cout << syscall(SYS_gettid) << " " << i + << "f = " << std::hex << &f << std::endl; +} +#endif + +void *doit(void *arg) { + int *loopcnt = reinterpret_cast(arg); + std::vector f; + + for (int i = 0; i < *loopcnt; ++i) { + std::vector g = doStuff(f, i); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/mttest2.cpp b/test/mttest2.cpp new file mode 100644 index 0000000..2f91c38 --- /dev/null +++ b/test/mttest2.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mttest.h" +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +typedef union { + int i; + float f; + char c; +} UN; +typedef UN UN2; +typedef struct { + int i; + float f; + char c; +} ST; +typedef ST ST2; + +class FooParent { + int aa; +}; + +class Foo : FooParent { + public: + bool aBool; + short aShort; + std::vector> vectorOfStr; + std::multimap intToStrMultiMap; + std::map map; + std::unordered_map + unorderedMap; + char arr[10]; + int aa; + int bb : 1; + int : 0; + int cc : 5; + int dd : 30; + + // Bar bar_arr[5]; + int ref; + std::function testFunc; + std::deque testDeque; + + std::queue> testQueue; +}; + +Foo myGlobalFoo; +// std::unique_ptr myGlobalFoo; + +// pass in the loop counter in the args +#ifdef MTTEST2_INLINE_DO_STUFF +static inline __attribute__((always_inline)) +#endif +std::vector +doStuff(Foo &foo, std::vector> &m, + std::vector &f, + std::vector> &p) { + std::vector altvect = {1, 3, 5, 7}; + foo.ref++; + myGlobalFoo.vectorOfStr.push_back(std::string("Test String")); + + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + std::cout << " addr of m = " << reinterpret_cast(&m) << std::endl; + std::cout << " addr of p = " << reinterpret_cast(&p) << std::endl; + std::cout << " addr of myGlobalFoo = " + << reinterpret_cast(&myGlobalFoo) << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + return newvect; +} + +void doStuff(std::vector &f, int i) { + f.push_back(i); + std::cout << "Entries in f: " << f.size() << std::endl; +} + +void doNothing() { + std::cout << "I do nothing, the function does nothing" << std::endl; +} + +void *doit(void *arg) { + doNothing(); + int *loopcnt = reinterpret_cast(arg); + std::vector f; + f.reserve(200); + std::vector> mv; + + std::unordered_map m; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = "ba"; + m["a"] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbaaaaaaaaaaa"] = "bbb"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccaaaaaaaa"] = "bbbb"; + + mv.push_back(m); + mv.push_back(m); + mv.push_back(m); + + std::vector> pv; + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdefghi", 10); + pv.push_back(p); + } + + for (int i = 0; i < *loopcnt; ++i) { + for (int j = 0; j < 3; j++) { + f.push_back("abcdefghijklmn"); + } + for (int j = 0; j < 3; j++) { + f.push_back( + "abcdefghijklmnoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + Foo foo; + + std::multimap mm; + + mm.insert(std::pair(0, 0)); + mm.insert(std::pair(1, 10)); + mm.insert(std::pair(2, 20)); + mm.insert(std::pair(3, 30)); + + mm.insert(std::pair(1, 100)); + + foo.intToStrMultiMap = mm; + + /*foo.vectorOfStr = f; + foo.mapOfStr = mv; + foo.vectorOfPair = pv;*/ + /*foo.bar_arr[0].bar = ""; + foo.bar_arr[1].bar = "0123456789"; + foo.bar_arr[2].bar = "01234567890123456789"; + foo.bar_arr[3].bar = "0123456789012345678901234567890123456789"; + foo.bar_arr[4].bar = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789";*/ + + foo.testFunc = [](int n) { std::cout << n << std::endl; }; + foo.testQueue.push(1); + foo.testQueue.push(2); + foo.testQueue.push(3); + + foo.testDeque.push_back(5); + + std::vector> dummy; + std::vector g = doStuff(foo, dummy, f, pv); + doStuff(g, i); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/mttest2_do_it_arg0_script_void_pointer.oid b/test/mttest2_do_it_arg0_script_void_pointer.oid new file mode 100644 index 0000000..ddc7a02 --- /dev/null +++ b/test/mttest2_do_it_arg0_script_void_pointer.oid @@ -0,0 +1 @@ +entry:_Z4doitPv:arg0 diff --git a/test/mttest2_do_stuff_arg0_script.oid b/test/mttest2_do_stuff_arg0_script.oid new file mode 100644 index 0000000..0e3ea0c --- /dev/null +++ b/test/mttest2_do_stuff_arg0_script.oid @@ -0,0 +1 @@ +entry:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0 diff --git a/test/mttest2_do_stuff_arg0_script_comments.oid b/test/mttest2_do_stuff_arg0_script_comments.oid new file mode 100644 index 0000000..e24ab42 --- /dev/null +++ b/test/mttest2_do_stuff_arg0_script_comments.oid @@ -0,0 +1,14 @@ +// Comment exemple at the start of a line +/* Single-line comment */ +/* Multi-lines + * comment + */ +/* ****** **//**//***/ +/*//*/ +// /* +// */ +entry:/* Within-line comment */_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg2 // Comment at the end of a line +// End of probe list +/* No more probes + * to see + * here */ diff --git a/test/mttest2_do_stuff_arg0_script_spaces.oid b/test/mttest2_do_stuff_arg0_script_spaces.oid new file mode 100644 index 0000000..d5737fc --- /dev/null +++ b/test/mttest2_do_stuff_arg0_script_spaces.oid @@ -0,0 +1 @@ +entry :_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0 diff --git a/test/mttest2_inline.cpp b/test/mttest2_inline.cpp new file mode 120000 index 0000000..83221de --- /dev/null +++ b/test/mttest2_inline.cpp @@ -0,0 +1 @@ +mttest2.cpp \ No newline at end of file diff --git a/test/mttest2_inline_script.oid b/test/mttest2_inline_script.oid new file mode 100644 index 0000000..5df8b06 --- /dev/null +++ b/test/mttest2_inline_script.oid @@ -0,0 +1 @@ +entry:_ZL7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg0 diff --git a/test/mttest3.cpp b/test/mttest3.cpp new file mode 100644 index 0000000..8ee3302 --- /dev/null +++ b/test/mttest3.cpp @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +typedef union { + int i; + float f; + char c; +} UN; +typedef UN UN2; +typedef struct { + int i; + float f; + char c; +} ST; +typedef ST ST2; + +// typedef struct vec_struct VEC; + +struct vec_struct { + std::vector v; +}; + +class BaseFoo { + int base[64]; +}; + +enum Color { Red, Green, Blue }; + +#define DIM 3 + +class Foo : BaseFoo { + public: + enum Color { red, green, blue } col; + ST a[8][8]; + vec_struct b[DIM][DIM][DIM]; + Color color; + char c[10]; + // VEC d[10]; + std::unique_ptr ptr; + std::shared_ptr ptr2; + std::vector vectorOfStr; + std::vector> mapOfStr; + std::vector> vectorOfPair; + UN unionVar; + UN2 unionVar2; + ST structVar; + ST2 structVar2; + int ref; +}; + +// pass in the loop counter in the args +std::vector doStuff(Foo &foo, + std::vector> &m, + std::vector &f, + std::vector> &p) { + std::vector altvect = {1, 3, 5, 7}; + foo.ref++; + + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + std::cout << " addr of m = " << reinterpret_cast(&m) << std::endl; + std::cout << " addr of p = " << reinterpret_cast(&p) << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + return newvect; +} + +#if 0 +void doStuff(std::vector &f, int i) +{ + f.push_back(i); + + if (i > 1000000) + __asm__ volatile ("retq"); + + std::cout << syscall(SYS_gettid) << " " << i + << "f = " << std::hex << &f << std::endl; +} +#endif + +void *doit(void *arg) { + int *loopcnt = reinterpret_cast(arg); + std::vector f; + std::vector> mv; + + std::map m; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = "ba"; + m["a"] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbaaaaaaaaaaa"] = "bbb"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccaaaaaaaa"] = "bbbb"; + + mv.push_back(m); + mv.push_back(m); + mv.push_back(m); + + std::vector> pv; + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdefghi", 10); + pv.push_back(p); + } + + Foo foo; + + for (int i = 0; i < DIM; i++) { + for (int j = 0; j < DIM; j++) { + for (int k = 0; k < DIM; k++) { + int elems = i * DIM * DIM + j * DIM + k; + for (int m = 0; m < elems; m++) { + foo.b[i][j][k].v.push_back(10); + } + } + } + } + + for (int i = 0; i < *loopcnt; ++i) { + for (int j = 0; j < 3; j++) { + f.push_back("abcdefghijklmn"); + } + for (int j = 0; j < 3; j++) { + f.push_back( + "abcdefghijklmnoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + + foo.vectorOfStr = f; + foo.mapOfStr = mv; + foo.vectorOfPair = pv; + + std::vector g = doStuff(foo, mv, f, pv); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/mttest4.cpp b/test/mttest4.cpp new file mode 100644 index 0000000..e4ac473 --- /dev/null +++ b/test/mttest4.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +// #include "common/init/Init.h" + +static char textPad __attribute__((used)); + +typedef union { + int i; + float f; + char c; +} UN; +typedef UN UN2; +typedef struct { + int i; + float f; + char c; +} ST; +typedef ST ST2; +typedef int INT_TYPE; + +/*class Bar { + std::string bar; +};*/ + +class Foo { + public: + std::vector vectorOfStr; + std::multimap intToStrMultiMap; + /* std::vector> mapOfStr; + bool myb; + std::vector> vectorOfPair; */ + /*UN unionVar; + UN2 unionVar2; + ST structVar; + ST2 structVar2; + char t; */ + char arr[10]; + int aa; + int bb : 1; + int : 0; + int cc : 5; + int dd : 30; + + // Bar bar_arr[5]; + int ref; +}; + +Foo myGlobalFoo; +// std::unique_ptr myGlobalFoo; + +typedef INT_TYPE INT_ARRAY[10]; +enum ENUM { E_A }; + +// pass in the loop counter in the args +std::vector doStuff(ENUM e, ENUM *e_p, INT_TYPE int_type, + INT_TYPE &int_type_r, INT_TYPE *int_type_p, + INT_TYPE **int_type_pp, INT_ARRAY int_array, Foo &foo, + std::vector> &m, + std::vector &f, + std::vector> &p) { + std::vector altvect = {1, 3, 5, 7}; + foo.ref++; + myGlobalFoo.vectorOfStr.push_back(std::string("Test String")); + + std::cout << e << e_p << int_type << int_type_r << (uintptr_t)&int_type_p + << (uintptr_t)&int_type_pp << int_array; + std::cout << " doStuff entries: " << f.size() << std::endl; + std::cout << " addr of f = " << reinterpret_cast(&f) << std::endl; + std::cout << " addr of m = " << reinterpret_cast(&m) << std::endl; + std::cout << " addr of p = " << reinterpret_cast(&p) << std::endl; + std::cout << " addr of myGlobalFoo = " + << reinterpret_cast(&myGlobalFoo) << std::endl; + + std::vector newvect(altvect); + + std::cout << " addr of newvect = " << reinterpret_cast(&newvect) + << std::endl; + + return newvect; +} + +void doStuff(std::vector &f, int i) { + f.push_back(i); + std::cout << "Entries in f: " << f.size() << std::endl; +} + +void *doit(void *arg) { + int *loopcnt = reinterpret_cast(arg); + std::vector f; + f.reserve(200); + std::vector> mv; + + std::unordered_map m; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = "ba"; + m["a"] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbaaaaaaaaaaa"] = "bbb"; + m["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccaaaaaaaa"] = "bbbb"; + + mv.push_back(m); + mv.push_back(m); + mv.push_back(m); + + std::vector> pv; + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef", 10); + pv.push_back(p); + } + { + std::pair p( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdefghi", 10); + pv.push_back(p); + } + + for (int i = 0; i < *loopcnt; ++i) { + for (int j = 0; j < 3; j++) { + f.push_back("abcdefghijklmn"); + } + for (int j = 0; j < 3; j++) { + f.push_back( + "abcdefghijklmnoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + Foo foo; + + std::multimap mm; + + mm.insert(std::pair(0, 0)); + mm.insert(std::pair(1, 10)); + mm.insert(std::pair(2, 20)); + mm.insert(std::pair(3, 30)); + + mm.insert(std::pair(1, 100)); + + foo.intToStrMultiMap = mm; + + /*foo.vectorOfStr = f; + foo.mapOfStr = mv; + foo.vectorOfPair = pv;*/ + /*foo.bar_arr[0].bar = ""; + foo.bar_arr[1].bar = "0123456789"; + foo.bar_arr[2].bar = "01234567890123456789"; + foo.bar_arr[3].bar = "0123456789012345678901234567890123456789"; + foo.bar_arr[4].bar = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789";*/ + + std::vector> dummy; + INT_TYPE int_type; + INT_TYPE *int_type_p; + // INT_TYPE arr[10]; + INT_ARRAY int_arr; + ENUM enum_type; + std::vector g = + doStuff(enum_type, &enum_type, int_type, int_type, int_type_p, + &int_type_p, int_arr, foo, dummy, f, pv); + doStuff(g, i); + + std::cout << "Number of elems = " << g.size() << std::endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + + f.clear(); + } + + pthread_exit(arg); +} + +int main(int argc, char *argv[]) { + int i = 0; + int err; + pthread_t tid[2]; + char *b; + + // facebook::initFacebook(&argc, &argv); + + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + std::cout << "main thread = " << syscall(SYS_gettid) << " pid = " << getpid() + << std::endl; + sleep(1); + + std::map mapOfWords; + mapOfWords.insert(std::make_pair("earth", 1)); + mapOfWords.insert(std::make_pair("moon", 2)); + mapOfWords["sun"] = 3; + + std::vector nameList; + nameList.push_back("The quick brown fox"); + nameList.push_back("jumps over "); + nameList.push_back("the "); + nameList.push_back("lazy dog "); + + for (auto it = nameList.begin(); it != nameList.end(); it++) { + std::cout << "nameList: " << *it << " size: " << it->size() << std::endl; + } + + std::cout << "mapOfWords #elements: " << mapOfWords.size() << std::endl; + + int size = 0; + + for (auto it = mapOfWords.begin(); it != mapOfWords.end(); ++it) { + size += it->first.size(); + } + + std::cout << "mapOfWords map addr = " << &mapOfWords << std::endl; + std::cout << "nameList vector addr = " << &nameList << std::endl; + + for (int i = 0; i < 1; ++i) { + err = pthread_create(&(tid[i]), NULL, &doit, (void **)&loopcnt); + + if (err != 0) { + std::cout << "Failed to create thread:[ " << strerror(err) << " ]" + << std::endl; + } + } + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + for (int i = 0; i < 1; ++i) { + pthread_join(tid[i], (void **)&b); + } + + exit(EXIT_SUCCESS); +} diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp new file mode 100644 index 0000000..feabfe5 --- /dev/null +++ b/test/test_compiler.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "OICompiler.h" + +namespace fs = std::filesystem; + +/* Add the suffix operator _b to easily create array of uint8_t. */ +static constexpr uint8_t operator"" _b(unsigned long long n) { + return uint8_t(n); +} + +typedef int (*jitFunc)(void); + +TEST(CompilerTest, CompileAndRelocate) { + auto symbols = std::make_shared(getpid()); + + auto code = R"( + // Use `extern "C"` to prevent mangled symbol names + extern "C" { + static int xs[] = {1, 2, 3, 4, 5}; + int sumXs() { + // Use `volatile` to prevent the compiler from optimising out the loop + volatile int sum = 0; + for (int i = 0; i < 5; i++) + sum += xs[i]; + return sum; + } + + int constant() { return 42; } + } + )"; + + auto tmpdir = fs::temp_directory_path() / "test-XXXXXX"; + EXPECT_NE(mkdtemp(const_cast(tmpdir.c_str())), nullptr); + + auto sourcePath = tmpdir / "src.cpp"; + auto objectPath = tmpdir / "obj.o"; + + OICompiler compiler{symbols, {}}; + + EXPECT_TRUE(compiler.compile(code, sourcePath, objectPath)); + + const size_t relocSlabSize = 4096; + void *relocSlab = + mmap(nullptr, relocSlabSize, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + EXPECT_NE(relocSlab, nullptr); + + auto relocResult = + compiler.applyRelocs((uintptr_t)relocSlab, {objectPath}, {}); + EXPECT_TRUE(relocResult.has_value()); + + auto &[_, segs, jitSymbols] = relocResult.value(); + + EXPECT_EQ(segs.size(), 1); + EXPECT_EQ(jitSymbols.size(), 3); // 2 functions + 1 static array + + { + const auto &lastSeg = segs.back(); + auto lastSegLimit = lastSeg.RelocAddr + lastSeg.Size; + auto relocLimit = (uintptr_t)relocSlab + relocSlabSize; + EXPECT_LE(lastSegLimit, relocLimit); + } + + for (const auto &[Base, Reloc, Size] : segs) + std::memcpy((void *)Reloc, (void *)Base, Size); + + { + auto symAddr = jitSymbols.at("constant"); + EXPECT_EQ(((jitFunc)symAddr)(), 42); + } + + { + auto symAddr = jitSymbols.at("sumXs"); + EXPECT_EQ(((jitFunc)symAddr)(), 15); + } + + munmap(relocSlab, relocSlabSize); +} + +TEST(CompilerTest, CompileAndRelocateMultipleObjs) { + auto symbols = std::make_shared(getpid()); + + auto codeX = R"( + extern "C" { + static int xs[] = {1, 2, 3, 4, 5}; + int sumXs() { + volatile int sum = 0; + for (int i = 0; i < 5; i++) + sum += xs[i]; + return sum; + } + } + )"; + + auto codeY = R"( + extern "C" { + static int ys[] = {10, 11, 12, 13, 14, 15}; + int sumYs() { + volatile int sum = 0; + for (int i = 0; i < 6; i++) + sum += ys[i]; + return sum; + } + } + )"; + + auto tmpdir = fs::temp_directory_path() / "test-XXXXXX"; + EXPECT_NE(mkdtemp(const_cast(tmpdir.c_str())), nullptr); + + auto sourceXPath = tmpdir / "srcX.cpp"; + auto objectXPath = tmpdir / "objX.o"; + + auto sourceYPath = tmpdir / "srcY.cpp"; + auto objectYPath = tmpdir / "objY.o"; + + OICompiler compiler{symbols, {}}; + EXPECT_TRUE(compiler.compile(codeX, sourceXPath, objectXPath)); + EXPECT_TRUE(compiler.compile(codeY, sourceYPath, objectYPath)); + + const size_t relocSlabSize = 8192; + void *relocSlab = + mmap(nullptr, relocSlabSize, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + EXPECT_NE(relocSlab, nullptr); + + auto relocResult = compiler.applyRelocs((uintptr_t)relocSlab, + {objectXPath, objectYPath}, {}); + EXPECT_TRUE(relocResult.has_value()); + + auto &[_, segs, jitSymbols] = relocResult.value(); + + EXPECT_EQ(segs.size(), 2); + EXPECT_EQ(jitSymbols.size(), 4); // 2 functions + 2 static array + + { + const auto &lastSeg = segs.back(); + auto lastSegLimit = lastSeg.RelocAddr + lastSeg.Size; + auto relocLimit = (uintptr_t)relocSlab + relocSlabSize; + EXPECT_LE(lastSegLimit, relocLimit); + } + + for (const auto &[Base, Reloc, Size] : segs) + std::memcpy((void *)Reloc, (void *)Base, Size); + + { + auto symAddr = jitSymbols.at("sumXs"); + EXPECT_EQ(((jitFunc)symAddr)(), 15); + } + + { + auto symAddr = jitSymbols.at("sumYs"); + EXPECT_EQ(((jitFunc)symAddr)(), 75); + } + + munmap(relocSlab, relocSlabSize); +} + +TEST(CompilerTest, LocateOpcodes) { + const std::array retInsts = { + std::array{0xC2_b}, /* Return from near procedure, with immediate value */ + std::array{0xC3_b}, /* Return from near procedure */ + std::array{0xCA_b}, /* Return from far procedure, with immediate value */ + std::array{0xCB_b}, /* Return from far procedure */ + }; + + { /* Can trivially locate 0xC2? */ + const std::array insts = {0xC2_b, 0xAA_b, 0xBB_b}; + auto locs = OICompiler::locateOpcodes(insts, retInsts); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 1); + ASSERT_EQ(locs->at(0), 0); + } + + { /* Can trivially locate 0xC3? */ + const std::array insts = {0xC3_b}; + auto locs = OICompiler::locateOpcodes(insts, retInsts); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 1); + ASSERT_EQ(locs->at(0), 0); + } + + { /* Can trivially locate 0xCA? */ + const std::array insts = {0xCA_b, 0xAA_b, 0xBB_b}; + auto locs = OICompiler::locateOpcodes(insts, retInsts); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 1); + ASSERT_EQ(locs->at(0), 0); + } + + { /* Can trivially locate 0xCB? */ + const std::array insts = {0xCB_b}; + auto locs = OICompiler::locateOpcodes(insts, retInsts); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 1); + ASSERT_EQ(locs->at(0), 0); + } + + // clang-format off + const std::array insts = { + 0x55_b, /* push rbp */ + 0xba_b,0x22_b,0x00_b,0x00_b,0x00_b, /* mov edx,0x22 */ + 0x48_b,0x89_b,0xe5_b, /* mov rbp,rsp */ + 0x41_b,0x54_b, /* push r12 */ + 0x49_b,0x89_b,0xf4_b, /* mov r12,rsi */ + 0xbe_b,0x78_b,0xcd_b,0x20_b,0x00_b, /* mov esi,0x214710 */ + 0x48_b,0x83_b,0xec_b,0x18_b, /* sub rsp,0x18 */ + 0x89_b,0x7d_b,0xec_b, /* mov DWORD PTR [rbp-0x14],edi */ + 0xbf_b,0xc0_b,0xe4_b,0x2c_b,0x00_b, /* mov edi,0x3fa6c0 */ + 0xe8_b,0xac_b,0x0e_b,0x07_b,0x00_b, /* call 0x3eb260 <_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l@plt> */ + 0x4c_b,0x89_b,0xe6_b, /* mov rsi,r12 */ + 0x48_b,0x8d_b,0x7d_b,0xec_b, /* lea rdi,[rbp-0x14] */ + 0xe8_b,0x40_b,0x96_b,0x02_b,0x00_b, /* call 0x2ade60 <_ZN7testing14InitGoogleMockEPiPPc> */ + 0xe8_b,0x3b_b,0x3b_b,0x04_b,0x00_b, /* call 0x2c80d0 <_ZN7testing8UnitTest11GetInstanceEv> */ + 0x48_b,0x89_b,0xc7_b, /* mov rdi,rax */ + 0xe8_b,0x73_b,0x18_b,0x06_b,0x00_b, /* call 0x2e5e10 <_ZN7testing8UnitTest3RunEv> */ + 0x48_b,0x83_b,0xc4_b,0x18_b, /* add rsp,0x18 */ + 0x41_b,0x5c_b, /* pop r12 */ + 0x5d_b, /* pop rbp */ + 0xc3_b, /* ret */ + }; + // clang-format on + + { /* Empty needles is ok? */ + const std::array, 0> needles; + auto locs = OICompiler::locateOpcodes(insts, needles); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 0); + } + + { /* Empty text is ok? */ + const std::array emptyText; + auto locs = OICompiler::locateOpcodes(emptyText, retInsts); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 0); + } + + { /* Doesn't locate bytes within an instruction */ + const std::array, 1> needles = {{{0x00_b}}}; + auto locs = OICompiler::locateOpcodes(insts, needles); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 0); + } + + { /* Large range of differently sized needles */ + const std::array needles = { + std::vector{0x41_b, 0x54_b}, /* push r12: 1 instance */ + std::vector{0x48_b, 0x83_b, 0xec_b, + 0x18_b}, /* sub rsp,0x18: 1 instance */ + std::vector(1, 0xe8_b), /* call: 4 instances */ + std::vector{0x41_b, 0x5c_b}, /* pop r12: 1 instance */ + }; + auto locs = OICompiler::locateOpcodes(insts, needles); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 7); + ASSERT_EQ(locs->at(0), 9); + ASSERT_EQ(locs->at(1), 19); + ASSERT_EQ(locs->at(2), 31); + ASSERT_EQ(locs->at(3), 43); + ASSERT_EQ(locs->at(4), 48); + ASSERT_EQ(locs->at(5), 56); + ASSERT_EQ(locs->at(6), 65); + } + + { /* No double counting */ + const std::array needles = { + std::vector(1, 0xba_b), + std::vector(1, 0xba_b), + std::vector{0xba_b, 0x22_b}, + }; + auto locs = OICompiler::locateOpcodes(insts, needles); + ASSERT_TRUE(locs.has_value()); + ASSERT_EQ(locs->size(), 1); + ASSERT_EQ(locs->at(0), 1); + } +} diff --git a/test/test_parser.cpp b/test/test_parser.cpp new file mode 100644 index 0000000..3954bcf --- /dev/null +++ b/test/test_parser.cpp @@ -0,0 +1,216 @@ +#include +#include +#include + +#include + +#include "OILexer.h" +#include "OIParser.h" + +using ::testing::HasSubstr; + +using namespace ObjectIntrospection; + +// Utilities +static ParseData parseString(const std::string &script) { + ParseData pdata; + std::istringstream input{script}; + OIScanner scanner{&input}; + OIParser parser{scanner, pdata}; + + EXPECT_EQ(parser.parse(), 0); + + return pdata; +} + +static std::string parseBadString(const std::string &script) { + ParseData pdata; + std::istringstream input{script}; + OIScanner scanner{&input}; + OIParser parser{scanner, pdata}; + + testing::internal::CaptureStderr(); + EXPECT_NE(parser.parse(), 0); + return testing::internal::GetCapturedStderr(); +} + +static void EXPECT_REQ_EQ(const prequest &req, const std::string &type, + const std::string &func, + const std::vector &args) { + EXPECT_EQ(req.type, type); + EXPECT_EQ(req.func, func); + EXPECT_EQ(req.args, args); +} + +// Unit Tests +TEST(ParserTest, EntryParsing) { + { // Entry probe with arg0 + ParseData pdata = parseString("entry:function:arg0"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "function", {"arg0"}); + } + + { // Entry probe with this + ParseData pdata = parseString("entry:function:this"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "function", {"this"}); + } +} + +TEST(ParserTest, ReturnParsing) { + { // Return probe with arg + ParseData pdata = parseString("return:function:arg0"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "return", "function", {"arg0"}); + } + + { // Return probe with this + ParseData pdata = parseString("return:function:this"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "return", "function", {"this"}); + } + + { // Return probe with retval + ParseData pdata = parseString("return:function:retval"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "return", "function", {"retval"}); + } +} + +TEST(ParserTest, GlobalParsing) { + ParseData pdata = parseString("global:varName"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "global", "varName", {}); +} + +TEST(ParserTest, MangledFunc) { + { + ParseData pdata = parseString( + "entry:_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_" + "stringIcSt11char_traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_" + "EEESaISF_EERS1_IS8_SaIS8_EERS1_ISB_IS8_dESaISM_EE:arg9"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ( + pdata.getReq(0), "entry", + "_Z7doStuffR3FooRSt6vectorISt3mapINSt7__cxx1112basic_stringIcSt11char_" + "traitsIcESaIcEEES8_St4lessIS8_ESaISt4pairIKS8_S8_EEESaISF_EERS1_IS8_" + "SaIS8_EERS1_ISB_IS8_dESaISM_EE", + {"arg9"}); + } + + { + ParseData pdata = parseString("entry:func.with.dots.1234:arg0"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "func.with.dots.1234", {"arg0"}); + } +} + +TEST(ParserTest, IgnoreWhitespaces) { + ParseData pdata = parseString( + "\n" + "entry\n" + " :function \n" + " : arg1 "); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "function", {"arg1"}); +} + +TEST(ParserTest, IgnoreComments) { + ParseData pdata = parseString( + "// Testing comments\n" + "/* Multi-lines comments\n" + " * Also work! */\n" + "entry/* Location of the probe */:" + "function/* Name of the probed function */:" + "arg8// Function argument to probe"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "function", {"arg8"}); +} + +TEST(ParserTest, MultipleProbes) { + ParseData pdata = parseString( + "entry:f1:arg0 " + "return:f2:arg1 " + "global:g1 " + "return:f3:retval"); + EXPECT_EQ(pdata.numReqs(), 4); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "f1", {"arg0"}); + EXPECT_REQ_EQ(pdata.getReq(1), "return", "f2", {"arg1"}); + EXPECT_REQ_EQ(pdata.getReq(2), "global", "g1", {}); + EXPECT_REQ_EQ(pdata.getReq(3), "return", "f3", {"retval"}); +} + +TEST(ParserTest, MultipleArgs) { + ParseData pdata = parseString("entry:f1:arg0,arg1,arg2"); + EXPECT_EQ(pdata.numReqs(), 1); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "f1", {"arg0", "arg1", "arg2"}); +} + +TEST(ParserTest, MultipleProbesAndArgs) { + ParseData pdata = parseString( + "entry:f1:arg0,arg1,arg2 " + "return:f2:arg9,arg8"); + EXPECT_EQ(pdata.numReqs(), 2); + EXPECT_REQ_EQ(pdata.getReq(0), "entry", "f1", {"arg0", "arg1", "arg2"}); + EXPECT_REQ_EQ(pdata.getReq(1), "return", "f2", {"arg9", "arg8"}); +} + +TEST(ParserTest, NoProbe) { + EXPECT_THAT( + parseBadString(""), + HasSubstr("OI Parse Error: syntax error, unexpected OI_EOF, expecting " + "OI_PROBETYPE at 1.1")); +} + +TEST(ParserTest, InvalidProbeType) { + EXPECT_THAT( + parseBadString("arg0:f1:arg1"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_ARG, expecting " + "OI_PROBETYPE at 1.1-4")); + EXPECT_THAT( + parseBadString("retval:f1:arg1"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_ARG, expecting " + "OI_PROBETYPE at 1.1-6")); + EXPECT_THAT( + parseBadString("xyz:f1:arg1"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, expecting " + "OI_PROBETYPE at 1.1-3")); +} + +TEST(ParserDeathTest, InvalidFunc) { + // The lexer exits on error, using a death test for that case + EXPECT_DEATH(parseString("entry:bad-mangled:arg0"), "flex scanner jammed"); + + EXPECT_THAT( + parseBadString("entry:bad mangled:arg0"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, expecting " + "OI_EOF or OI_COLON or OI_PROBETYPE at 1.11-17")); + EXPECT_THAT( + parseBadString("entry:bad/***/mangled:arg0"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, expecting " + "OI_EOF or OI_COLON or OI_PROBETYPE at 1.15-21")); +} + +TEST(ParserTest, InvalidArg) { + EXPECT_THAT(parseBadString("entry:f1:arg99"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, " + "expecting OI_ARG at " + "1.10-14")); + EXPECT_THAT(parseBadString("entry:f1:arg-1"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, " + "expecting OI_ARG at " + "1.10-12")); + EXPECT_THAT( + parseBadString("entry:f1:xyz"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, expecting " + "OI_ARG at 1.10-12")); + EXPECT_THAT(parseBadString("return:f2:ret"), + HasSubstr("OI Parse Error: syntax error, unexpected OI_FUNC, " + "expecting OI_ARG at " + "1.11-13")); +} + +TEST(ParserTest, UnterminatedComment) { + EXPECT_THAT(parseBadString("entry:f1:arg0 /*"), + HasSubstr("OI Parse Error: unterminated /* comment at 1.15-16")); +} diff --git a/test/userDef1.cpp b/test/userDef1.cpp new file mode 100644 index 0000000..c6a3b10 --- /dev/null +++ b/test/userDef1.cpp @@ -0,0 +1,44 @@ +#include + +#include +#include +#include +#include + +typedef struct userDef { + int var1; + char var2; + void *var3; + char var4[256]; +} userDef; + +int main(int argc, char *argv[]) { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + userDef udefs[10]; + + int loopcnt = atoi(argv[1]); + + std::vector userDefList; + + for (int i = 0; i < 10; ++i) { + userDefList.push_back(userDef()); + } + + int i = 0; + for (auto udef : userDefList) { + std::cout << "userDefList[" << i << "]= " << sizeof(udef) << std::endl; + } + + std::cout << "pid = " << getpid() + << " Address of userDefList = " << &userDefList << std::endl; + ; + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } +} diff --git a/test/vector.cpp b/test/vector.cpp new file mode 100644 index 0000000..551a87d --- /dev/null +++ b/test/vector.cpp @@ -0,0 +1,115 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +class Emp { + std::string name; + int age; + std::vector colleagues; + + public: + Emp(std::string name1) { + name = name1; + }; + void setName(std::string name1) { + name = name1; + std::cout << "name: " << name << std::endl; + }; + std::string getName(void) { + return name; + }; + void setAge(int age) { + age = age; + }; + void addColleague(Emp p) { + colleagues.emplace(colleagues.begin(), p); + }; + std::vector getColleagues(void) { + return colleagues; + } +}; + +class Comp { + std::string name; + std::vector companies; + + public: + Comp(std::string name1) { + name = name1; + }; + std::vector getComps(void) { + return companies; + } +}; + +void foo(class Emp& me) { + std::vector colleagues = me.getColleagues(); + int size = colleagues.size(); + std::cout << size << std::endl; + std::cout << colleagues[4].getName().size() << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + exit(1); + } + + int loopcnt = atoi(argv[1]); + + Emp Jon("Jon Haslam"); + Emp Mark(std::string("Mark Santaniello")); + Emp Kalyan(std::string("Kalyan Saladi")); + Emp Banit(std::string("Banit Agrawal")); + Emp Harit(std::string("Harit Modi")); + Emp Amlan(std::string("Amlan Nayak")); + Emp Blaise(std::string("Blaise Sanouillet")); + + Jon.addColleague(Mark); + Jon.addColleague(Kalyan); + Jon.addColleague(Banit); + Jon.addColleague(Harit); + Jon.addColleague(Amlan); + Jon.addColleague(Blaise); + + std::vector TestVec{10, 20, 30, 20, 10, 40}; + + int total = 0; + for (auto it = TestVec.begin(); it != TestVec.end(); it++) { + total += *it; + } + + std::cout << "Total = " + << " " << total << std::endl; + + Comp bus("facebook"); + std::vector m = bus.getComps(); + std::cout << "Number of Comps: " << m.size() << std::endl; + + std::vector mycol = Jon.getColleagues(); + + for (auto it = mycol.begin(); it != mycol.end(); ++it) { + std::cout << "Colleague: " << it->getName().size() + << " size: " << sizeof(*it) << std::endl; + } + + // std::cout << "mycol addr = " << &mycol << std::endl; + std::cout << "TestVec addr = " << &TestVec << std::endl; + // std::cout << "Number of Colleagues: " << mycol.size() << std::endl; + + std::cout << "pid == " << getpid() << " (hit RETURN to continue)" + << std::endl; + + for (int i = 0; i < loopcnt; i++) { + std::cout << "i: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + foo(Jon); +} diff --git a/tools/OILGen.cpp b/tools/OILGen.cpp new file mode 100644 index 0000000..12f1408 --- /dev/null +++ b/tools/OILGen.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +#include +#include + +#include "OICodeGen.h" +#include "OIGenerator.h" +#include "OIOpts.h" + +namespace fs = std::filesystem; +using namespace ObjectIntrospection; + +constexpr static OIOpts opts{ + OIOpt{'h', "help", no_argument, nullptr, "Print this message and exit."}, + OIOpt{'o', "output", required_argument, "", "Write output to file."}, + OIOpt{'c', "config-file", required_argument, "", + "Path to OI configuration file."}, + OIOpt{'d', "dump-jit", optional_argument, "", + "Write generated code to a file (for debugging)."}, +}; + +void usage() { + std::cerr << "usage: oilgen ARGS INPUT_OBJECT" << std::endl; + std::cerr << opts; + + std::cerr << std::endl + << "You probably shouldn't be calling this application directly. " + "It's meant to be" + << std::endl + << "called by a clang plugin automatically with BUCK." << std::endl; +} + +int main(int argc, char* argv[]) { + google::InitGoogleLogging(argv[0]); + FLAGS_minloglevel = 0; + FLAGS_stderrthreshold = 0; + + fs::path outputPath = "a.o"; + fs::path configFilePath = "/usr/local/share/oi/base.oid.toml"; + fs::path sourceFileDumpPath = ""; + + int c; + while ((c = getopt_long(argc, argv, opts.shortOpts(), opts.longOpts(), + nullptr)) != -1) { + switch (c) { + case 'o': + outputPath = optarg; + break; + case 'c': + configFilePath = optarg; + break; + case 'd': + sourceFileDumpPath = optarg != nullptr ? optarg : "jit.cpp"; + break; + } + } + + if (optind >= argc) { + usage(); + return EXIT_FAILURE; + } + fs::path primaryObject = argv[optind]; + + OIGenerator oigen; + + oigen.setOutputPath(std::move(outputPath)); + oigen.setConfigFilePath(std::move(configFilePath)); + oigen.setSourceFileDumpPath(sourceFileDumpPath); + + return oigen.generate(primaryObject); +} diff --git a/tools/OIP.cpp b/tools/OIP.cpp new file mode 100644 index 0000000..d2aea0f --- /dev/null +++ b/tools/OIP.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 +#include +#include +#include +#include + +#include "OICompiler.h" +#include "PaddingHunter.h" +#include "Serialize.h" + +namespace fs = std::filesystem; + +void usage(char* name) { + printf("usage: %s \n", name); +} + +void printDrgnType(const struct drgn_type* type) { + printf("{"); + printf("\"this\":\"%p\",", static_cast(type)); + printf("\"kind\":%d,", type->_private.kind); + printf("\"name\":\"%s\",", type->_private.name); + printf("\"size\":%zu", type->_private.size); + printf("}"); +} + +void printDrgnClassMemberInfo(const DrgnClassMemberInfo& m) { + printf("{"); + printf("\"type\":"); + printDrgnType(m.type); + printf(","); + printf("\"member_name\":\"%s\",", m.member_name.c_str()); + printf("\"bit_offset\":%zu,", m.bit_offset); + printf("\"bit_field_size\":%zu", m.bit_field_size); + printf("}"); +} + +void printClassMembersMap( + const std::map>& + classMembersMap) { + printf("{"); + bool isFirstItem = true; + for (const auto& [type, members] : classMembersMap) { + if (!isFirstItem) + printf(","); + printf("\"%p\":[", static_cast(type)); + { + bool isInnerFirstItem = true; + for (const auto& member : members) { + if (!isInnerFirstItem) + printf(","); + printDrgnClassMemberInfo(member); + isInnerFirstItem = false; + } + } + isFirstItem = false; + printf("]"); + } + printf("}"); +} + +void printContainerTypeMap( + const std::map< + struct drgn_type*, + std::pair>>& + containerTypeMap) { + printf("{"); + bool isFirstItem = true; + for (const auto& [type, pair] : containerTypeMap) { + if (!isFirstItem) + printf(","); + printf("\"%p\":[", static_cast(type)); + { + bool isInnerFirstItem = true; + for (const auto& container : pair.second) { + if (!isInnerFirstItem) + printf(","); + printDrgnType(container.type); + isInnerFirstItem = false; + } + } + printf("]"); + isFirstItem = false; + } + printf("}"); +} + +void printTypedefMap( + const std::map& typedefMap) { + printf("{"); + bool isFirstItem = true; + for (const auto& [typedefType, type] : typedefMap) { + if (!isFirstItem) + printf(","); + printf("\"%p\":", static_cast(typedefType)); + printDrgnType(type); + isFirstItem = false; + } + printf("}"); +} + +void printSizeMap(const std::map& sizeMap) { + printf("{"); + bool isFirstItem = true; + for (const auto& [typeName, size] : sizeMap) { + if (!isFirstItem) { + printf(","); + } + printf("\"%s\":%zu", typeName.c_str(), size); + isFirstItem = false; + } + printf("}"); +} + +void printDrgnTypeSet(const std::set& dummyList) { + printf("["); + bool isFirstItem = true; + for (const auto& type : dummyList) { + if (!isFirstItem) { + printf(","); + } + printDrgnType(type); + isFirstItem = false; + } + printf("]"); +} + +void printPaddedStruct( + std::map::value_type paddedStruct) { + printf("{\n"); + printf("Name: %s", paddedStruct.first.c_str()); + printf(", object size: %ld", paddedStruct.second.structSize); + printf(", saving size: %ld", paddedStruct.second.savingSize); + printf(", padding size: %ld", paddedStruct.second.paddingSize); + printf(", isSet size: %ld", paddedStruct.second.isSetSize); + printf("\nSaving opportunity: %ld bytes\n\n", + paddedStruct.second.paddingSize * paddedStruct.second.instancesCnt); + printf("%s\n", paddedStruct.second.definition.c_str()); + printf("}"); +} + +void printPaddedStructs( + const std::map& paddedStructs) { + printf("["); + bool isFirstItem = true; + for (const auto& paddedStruct : paddedStructs) { + if (!isFirstItem) + printf(","); + printPaddedStruct(paddedStruct); + isFirstItem = false; + } + printf("]"); +} + +void printTypeHierarchy(const TypeHierarchy& th) { + printf("{"); + printf("\"classMembersMap\":"); + printClassMembersMap(th.classMembersMap); + printf(","); + printf("\"containerTypeMap\":"); + printContainerTypeMap(th.containerTypeMap); + printf(","); + printf("\"typedefMap\":"); + printTypedefMap(th.typedefMap); + printf(","); + printf("\"sizeMap\":"); + printSizeMap(th.sizeMap); + printf(","); + printf("\"knownDummyTypeList\":"); + printDrgnTypeSet(th.knownDummyTypeList); + printf(","); + printf("\"pointerToTypeMap\":"); + // Re-using printTypedefMap to display pointerToTypeMap + printTypedefMap(th.pointerToTypeMap); + printf(","); + printf("\"thriftIssetStructTypes\":"); + printDrgnTypeSet(th.thriftIssetStructTypes); + printf("}\n"); +} + +void printFuncArg(const std::shared_ptr& funcObj) { + printf("{"); + printf("\"valid\":%s,", funcObj->valid ? "true" : "false"); + printf("\"typeName\":\"%s\"", funcObj->typeName.c_str()); + + const auto* funcArg = dynamic_cast(funcObj.get()); + if (funcArg != nullptr) { + printf(","); + printf("\"locations\":["); + for (size_t i = 0; i < funcArg->locator.locations_size; i++) { + if (i > 0) { + printf(","); + } + const auto& location = funcArg->locator.locations[i]; + printf("{\"start\":\"0x%zx\",\"end\":\"0x%zx\",\"expr_size\":%zu}", + location.start, location.end, location.expr_size); + } + printf("]"); + } + printf("}"); +} + +void printFuncDesc(const std::shared_ptr& funcDesc) { + printf("{"); + printf("\"symName\":\"%s\",", funcDesc->symName.c_str()); + printf("\"ranges\":["); + bool isFirstRange = true; + for (auto& range : funcDesc->ranges) { + if (!isFirstRange) { + printf(","); + } + printf("{\"start\": \"0x%zx\", \"end\": \"0x%zx\"}", range.start, + range.end); + isFirstRange = false; + } + printf("],"); + printf("\"isMethod\":%s,", funcDesc->isMethod ? "true" : "false"); + printf("\"funcArgs\":["); + + bool isFirstItem = true; + for (size_t i = 0; i < funcDesc->numArgs(); ++i) { + if (!isFirstItem) { + printf(","); + } + printFuncArg(funcDesc->getArgument(i)); + isFirstItem = false; + } + printf("],"); + printf("\"retval\":"); + if (funcDesc->retval) { + printFuncArg(funcDesc->retval); + } else { + printf("null"); + } + printf("}"); +} + +void printFuncDescs( + const std::unordered_map>& + funcDescs) { + printf("{"); + bool isFirstItem = true; + for (const auto& [name, funcDesc] : funcDescs) { + if (!isFirstItem) + printf(","); + printf("\"%s\":", name.c_str()); + printFuncDesc(funcDesc); + isFirstItem = false; + } + printf("}\n"); +} + +void printGlobalDesc(const std::shared_ptr& globalDesc) { + printf("{"); + printf("\"symName\":\"%s\",", globalDesc->symName.c_str()); + printf("\"typeName\":\"%s\",", globalDesc->typeName.c_str()); + printf("\"baseAddr\":\"%p\"", + reinterpret_cast(globalDesc->baseAddr)); + printf("}"); +} + +void printGlobalDescs( + const std::unordered_map>& + globalDescs) { + printf("{"); + bool isFirstItem = true; + for (const auto& [name, globalDesc] : globalDescs) { + if (!isFirstItem) { + printf(","); + } + printf("\"%s\":", name.c_str()); + printGlobalDesc(globalDesc); + isFirstItem = false; + } + printf("}"); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + usage(argv[0]); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + usage(argv[0]); + return EXIT_SUCCESS; + } + + fs::path cachePath = argv[1]; + if (!fs::exists(cachePath)) { + fprintf(stderr, "File not found: %s\n", cachePath.c_str()); + return EXIT_FAILURE; + } + std::ifstream cacheFile{cachePath}; + boost::archive::text_iarchive cacheArchive{cacheFile}; + + std::string buildID; + cacheArchive >> buildID; + printf("{\"buildID\":\"%s\",", buildID.c_str()); + + printf("\"data\":"); + if (cachePath.extension() == ".fd") { + std::unordered_map> funcDescs; + cacheArchive >> funcDescs; + printFuncDescs(funcDescs); + } else if (cachePath.extension() == ".gd") { + std::unordered_map> globalDescs; + cacheArchive >> globalDescs; + printGlobalDescs(globalDescs); + } else if (cachePath.extension() == ".th") { + std::pair, TypeHierarchy> + typeHierarchy; + cacheArchive >> typeHierarchy; + printTypeHierarchy(typeHierarchy.second); + } else if (cachePath.extension() == ".pd") { + std::map paddedStructs; + cacheArchive >> paddedStructs; + printPaddedStructs(paddedStructs); + } else { + fprintf(stderr, "Unknown file type: %s\n", cachePath.c_str()); + return EXIT_FAILURE; + } + printf("}\n"); + + return EXIT_SUCCESS; +} diff --git a/tools/OITB.cpp b/tools/OITB.cpp new file mode 100644 index 0000000..623f75d --- /dev/null +++ b/tools/OITB.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +#include +#include +#include +#include +#include + +#include "Common.h" +#include "OIOpts.h" +#include "PaddingHunter.h" +#include "Serialize.h" +#include "TreeBuilder.h" +#include "glog/vlog_is_on.h" + +namespace fs = std::filesystem; + +constexpr static OIOpts opts{ + OIOpt{'h', "help", no_argument, nullptr, "Print this message and exit"}, + OIOpt{'a', "log-all-structs", no_argument, nullptr, + "Enable TreeBuilder::Config::logAllStructs (=true)\n" + "Note: this option is already enabled, this is a no-op"}, + OIOpt{'n', "chase-raw-pointers", no_argument, nullptr, + "Enable TreeBuilder::Config::chaseRawPointers (=true)"}, + OIOpt{'w', "disable-padding-hunter", no_argument, nullptr, + "Disable TreeBuilder::Config::genPaddingStats (=false)"}, + OIOpt{'J', "dump-json", optional_argument, "[oid_out.json]", + "File to dump the results to, as JSON\n" + "(in addition to the default RocksDB output)"}, +}; + +static void usage(std::ostream &out) { + out << "Run TreeBuilder on the given data-segment dump.\n"; + out << "oitb aims to facilitate debugging issues from TreeBuilder, by " + "allowing local iterations and debugging.\n"; + out << "The options -a, -n, -w must match the settings used when collecting " + "the data-segment dump.\n"; + + out << "\nusage: oitb [opts...] [--] " + "\n"; + out << opts; + + out << std::endl; +} + +template +[[noreturn]] static void fatal_error(Args &&...args) { + std::cerr << "error: "; + (std::cerr << ... << args); + std::cerr << "\n\n"; + + usage(std::cerr); + exit(EXIT_FAILURE); +} + +static auto loadTypeInfo(fs::path thPath, fs::path pdPath) { + std::string cacheBuildId; // We don't check the build-id + std::pair th; + std::map pd; + + { /* Load TypeHierarchy */ + std::ifstream ifs(thPath); + boost::archive::text_iarchive ia(ifs); + + ia >> cacheBuildId; + ia >> th; + } + + { /* Load PaddingInfo */ + std::ifstream ifs(pdPath); + boost::archive::text_iarchive ia(ifs); + + ia >> cacheBuildId; + ia >> pd; + } + + return std::make_tuple(th.first, th.second, pd); +} + +static auto loadDatasegDump(fs::path datasegDumpPath) { + std::vector dataseg; + std::ifstream dump(datasegDumpPath); + + { /* Allocate enough memory to hold the dump */ + dump.seekg(0, std::ios_base::end); + auto dumpSize = dump.tellg(); + dump.seekg(0, std::ios_base::beg); + + assert(dumpSize % sizeof(decltype(dataseg)::value_type) == 0); + dataseg.resize(dumpSize / sizeof(decltype(dataseg)::value_type)); + } + + { /* Read the dump into the dataseg vector */ + auto datasegBytes = std::as_writable_bytes(std::span{dataseg}); + dump.read((char *)datasegBytes.data(), datasegBytes.size_bytes()); + } + + return dataseg; +} + +[[maybe_unused]] /* For debugging... */ +static std::ostream & +operator<<(std::ostream &out, TreeBuilder::Config tbc) { + out << "TreeBuilde::Config = ["; + out << "\n logAllStructs = " << tbc.logAllStructs; + out << "\n chaseRawPointers = " << tbc.chaseRawPointers; + out << "\n genPaddingStats = " << tbc.genPaddingStats; + out << "\n dumpDataSegment = " << tbc.dumpDataSegment; + out << "\n jsonPath = " << (tbc.jsonPath ? *tbc.jsonPath : "NONE"); + out << "\n]\n"; + return out; +} + +int main(int argc, char *argv[]) { + google::InitGoogleLogging(*argv); + google::LogToStderr(); + google::SetStderrLogging(google::INFO); + google::SetVLOGLevel("TreeBuilder", 3); + + /* Reflects `oid`'s defaults for TreeBuilder::Config */ + TreeBuilder::Config tbConfig{ + .logAllStructs = true, + .chaseRawPointers = false, + .genPaddingStats = true, + .dumpDataSegment = false, + .jsonPath = std::nullopt, + }; + + int c = '\0'; + while ((c = getopt_long(argc, argv, opts.shortOpts(), opts.longOpts(), + nullptr)) != -1) { + switch (c) { + case 'h': + usage(std::cout); + exit(EXIT_SUCCESS); + + case 'a': + tbConfig.logAllStructs = + true; // Weird that we're setting it to true, again... + break; + case 'n': + tbConfig.chaseRawPointers = true; + break; + case 'w': + tbConfig.genPaddingStats = false; + break; + case 'J': + tbConfig.jsonPath = optarg ? optarg : "oid_out.json"; + break; + + case ':': + fatal_error("missing option argument"); + case '?': + fatal_error("invalid option"); + default: + fatal_error("invalid option"); + } + } + + if (argc - optind < 3) + fatal_error("missing arguments"); + else if (argc - optind > 3) + fatal_error("too many arguments"); + + LOG(INFO) << "Loading type infos..."; + auto [rootType, typeHierarchy, paddingInfos] = + loadTypeInfo(argv[optind + 0], argv[optind + 1]); + + LOG(INFO) << "Loading data segment dump..."; + auto dataseg = loadDatasegDump(argv[optind + 2]); + + TreeBuilder typeTree(tbConfig); + + if (tbConfig.genPaddingStats) { + LOG(INFO) << "Setting-up PaddingHunter..."; + typeTree.setPaddedStructs(&paddingInfos); + } + + LOG(INFO) << "Running TreeBuilder..."; + typeTree.build(dataseg, rootType.varName, rootType.type.type, typeHierarchy); + + if (tbConfig.jsonPath) { + LOG(INFO) << "Writting JSON results to " << *tbConfig.jsonPath << "..."; + typeTree.dumpJson(); + } + + return EXIT_SUCCESS; +} diff --git a/tools/combine_configs.py b/tools/combine_configs.py new file mode 100644 index 0000000..50e359b --- /dev/null +++ b/tools/combine_configs.py @@ -0,0 +1,65 @@ +# +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. +# +import sys + +import toml + + +def main(): + if len(sys.argv) < 2: + print("usage: combine_configs.py OUTPUT_PATH INPUT_PATHS...") + return + + out = { + "types": { + "containers": [], + }, + "headers": { + "user_paths": [], + "system_paths": [], + }, + "codegen": { + "default_headers": set(), + "default_namespaces": set(), + }, + } + for cfg_path in sys.argv[2:]: + cfg = toml.load(cfg_path) + + types = cfg.get("types", None) + if types is not None: + out["types"]["containers"] += types.get("containers", []) + + headers = cfg.get("headers", None) + if headers is not None: + out["headers"]["user_paths"] += headers.get("user_paths", []) + out["headers"]["system_paths"] += headers.get("system_paths", []) + + codegen = cfg.get("codegen", None) + if codegen is not None: + out["codegen"]["default_headers"].update( + codegen.get("default_headers", set()) + ) + out["codegen"]["default_namespaces"].update( + codegen.get("default_namespaces", set()) + ) + + with open(sys.argv[1], "w") as f: + toml.dump(out, f) + + +if __name__ == "__main__": + main() diff --git a/tools/config_gen.py b/tools/config_gen.py new file mode 100755 index 0000000..c20ca40 --- /dev/null +++ b/tools/config_gen.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +# +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. + +import argparse +import getpass +import pathlib +import subprocess +import typing + +import toml + +STUB_CODE = b""" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +""" + +# Parts of the end of include paths to strip (eg: #include sys/param.h) +END_PATH_EXCLUDE = frozenset(("bits", "backward", "debug", "types", "sys", "gnu")) + + +def generate_compiler_preprocessed(compiler: str) -> str: + """Execute compiler to grab preprocessor output""" + out = subprocess.check_output([compiler, "-E", "-x", "c++", "-"], input=STUB_CODE) + return out.decode() + + +def generate_compiler_commands(compiler: str) -> str: + """Execute compiler to grab preprocessor output""" + out = subprocess.check_output( + [compiler, "-E", "-x", "c++", "-v", "-", "-o", "/dev/null"], + stderr=subprocess.STDOUT, + input=STUB_CODE, + ) + return out.decode() + + +def filter_includes_preprocessor( + preprocessor_output: str, +) -> typing.Tuple[typing.Iterable[str], typing.Iterable[str]]: + """Filter preprocessor output and extract include paths from it + + We are expecting lines like this, for user: + # 1 "/usr/include/c++/8/iostream" 1 3 + + where, + lineComps[0] - '#' + lineComps[1] - 'line number + lineComps[2] - 'file path + lineComps[3] - '1' (Entering new header file) + lineComps[4] - '3' (SrcMgr::C_System) + + see lib/Frontend/PrintPreprocessedOutput.cpp in clang source. + + Or like this, for system: + # 1 "/usr/include/features.h" 1 3 4 + + This time the consecutive [3 4] pattern indicate a + SrcMgr::C_ExternCSystem file. + """ + is_system, is_user = False, False + system_paths: typing.List[pathlib.Path] = [] + user_paths: typing.List[pathlib.Path] = [] + for line in preprocessor_output.splitlines(): + s = line.split() + if len(s) == 5 and s[3] == "1": + is_user = True + elif len(s) == 6 and s[3] == "1" and s[4] == "3" and s[5] == "4": + is_system = True + else: + continue + path = pathlib.Path(s[2].strip('"')) + + if not path.exists(): + continue + path = path.resolve().parent + while path.name in END_PATH_EXCLUDE: + path = path.parent + + if is_system and path not in system_paths: + system_paths.append(path) + elif is_user and path not in user_paths: + user_paths.append(path) + + # TODO the order of paths matter here - what's the right way to sort these lists? + # My best guess is that we need most specific to least specific, so let's just try the depth of the paths + system_paths.sort(key=lambda k: len(k.parents), reverse=True) + user_paths.sort(key=lambda k: len(k.parents), reverse=True) + + return (map(str, system_paths), map(str, user_paths)) + + +def filter_includes_commands(output: str) -> typing.Iterable[str]: + """Filter -v compiler output and retrieve include paths if possible. + + Unfortunately relies on non-standardized behavior... + + Example output we are parsing, to obtain the directories in order + ignoring nonexistent directory "/include" + #include "..." search starts here: + #include <...> search starts here: + /usr/local/include + /usr/lib64/clang/12.0.1/include + /usr/include + End of search list. + # 1 "" + # 1 "" 1 + # 1 "" 3 + # 341 "" 3 + # 1 "" 1 + # 1 "" 2 + # 1 "" 2 + + End of search list + """ + collecting = False + paths: typing.List[pathlib.Path] = [] + for line in output.splitlines(): + if collecting: + if line == "End of search list.": + break + # Just a backup - ideally this should never trigger + if line.startswith("#"): + continue + path = pathlib.Path(line.strip()) + path = path.resolve() + if path not in paths and path.exists(): + paths.append(path) + + if line.startswith("#include <...> search starts here:"): + collecting = True + continue + return map(str, paths) + + +def pull_base_toml() -> typing.Dict: + script = pathlib.Path(__file__) + repo_path = script.parent.parent + script = repo_path / "dev.oid.toml" + if not script.exists(): + raise RuntimeError( + "Base file dev.oid.toml not found, either replace it, or skip types." + ) + with open(script, "r") as f: + base = toml.load(f) + + # Now, we need to replace any placeholders that might be present in the base toml file with the real verisons. + user = getpass.getuser() + pwd = str(repo_path.resolve()) + + container_list = base.get("types", {}).get("containers") + if container_list: + for idx, c in enumerate(container_list): + container_list[idx] = c.replace("PWD", pwd).replace("USER", user) + + return base + + +def generate_toml( + system_paths: typing.Iterable[str], + user_paths: typing.Iterable[str], + base_object: typing.Dict, + output_file: str, +): + base_object.update( + {"headers": {"system_paths": system_paths, "user_paths": user_paths}} + ) + + with open(output_file, "w") as f: + toml.dump(base_object, f) + + +def main(): + parser = argparse.ArgumentParser( + description="Run a c/c++ compiler and attempt to generate an oi config file from the results" + ) + parser.add_argument( + "-c", + "--compiler", + default="clang++", + help="The compiler binary used to generate headers from.", + ) + parser.add_argument( + "--skip-types", + action="store_true", + help="Whether to skip pulling types from dev.oid.toml in addition to generating include headers.", + ) + parser.add_argument( + "--include-mode", + choices=("preprocessor", "commands"), + default="commands", + help="Which strategy to use for generating includes. Right now choose between using -E (preprocessor) or -v (verbose commands)", + ) + parser.add_argument( + "output_file", help="Toml file to output finished config file to." + ) + args = parser.parse_args() + + if args.include_mode == "preprocessor": + preprocessed = generate_compiler_preprocessed(args.compiler) + system_includes, user_includes = filter_includes_preprocessor(preprocessed) + elif args.include_mode == "commands": + output = generate_compiler_commands(args.compiler) + system_includes = filter_includes_commands(output) + user_includes = [] + else: + raise ValueError("Invalid include mode provided!") + + if args.skip_types: + base = {} + else: + base = pull_base_toml() + generate_toml(system_includes, user_includes, base, args.output_file) + + +if __name__ == "__main__": + main() diff --git a/tools/inline_stats.py b/tools/inline_stats.py new file mode 100755 index 0000000..9baef16 --- /dev/null +++ b/tools/inline_stats.py @@ -0,0 +1,367 @@ +# +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. +# +import atexit +import gc +import json +import re +import signal +import sqlite3 +import subprocess +import sys +from collections import defaultdict +from pathlib import Path + +import click +from elftools.common.py3compat import bytes2str +from elftools.elf.elffile import ELFFile + +GC_COUNTER_INIT = 0 +EXCLUDE_PATTERN = None +DB = None + +# README: +# This script depends on the python library pyelftools: https://github.com/eliben/pyelftools +# Please make sure you have it installed before trying it out. + +# For internal use, please see: https://fburl.com/code/1f6a4vjj + + +@atexit.register +def close_db(): + """ + Ensure that the database is properly closed before exiting + """ + if DB: + DB.commit() + DB.close() + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "--gc", + type=int, + default=10_000, + help="After how many CU memory is manually freed. Low values reduce performance. High values can cause OOM.", +) +@click.option( + "-o", + "--output", + type=click.Path(), + default="instats.db", + help="Name of the output SQLite3 database", +) +@click.option( + "-c/ ", + "--clear-db/--no-clear-db", + default=False, + help="Delete the database content on start", +) +@click.option( + "-e", + "--exclude", + default=r".^", # This pattern matches nothing, so no exclude by default + help="Exclude CUs whose path match the given pattern", +) +@click.option( + "-E", + "--exclude-file", + default=r".^", # This pattern matches nothing, so no exclude by default + help="Exclude files whose path match the given pattern", +) +@click.option( + "-S/ ", + "--follow-shared/--no-follow-shared", + default=False, + help="Traverse the shared library linked against the INPUTS", +) +@click.argument( + "inputs", nargs=-1, type=click.Path(exists=True) +) # help="Executable file to analyse" +def cli(gc, output, clear_db, exclude, exclude_file, follow_shared, inputs): + """ + Traverses the DWARF data of the INPUTS and count how many arguments have location info. + It counts for both function calls and inlined functions. + The results are stored in a SQLite3 database for convenient data analysis. + """ + + global GC_COUNTER_INIT + GC_COUNTER_INIT = gc + + global EXCLUDE_PATTERN + EXCLUDE_PATTERN = re.compile(exclude) + + exclude_file_pattern = re.compile(exclude_file) + ldd_pattern = re.compile(r".* => (.*) \(.*\)") + + create_db(output, clear_db) + + inputs = list(inputs) + processed_files = set() + for filename in inputs: + if exclude_file_pattern.search(filename) or filename in processed_files: + continue + + if follow_shared: + ldd = subprocess.run(["ldd", filename], capture_output=True, text=True) + shared_libraries = ldd_pattern.findall(ldd.stdout) + inputs.extend(shared_libraries) + + processed_files.add(filename) + process_file(filename) + + +def create_db(dbfilename, clear_db=False): + global DB + DB = sqlite3.connect(dbfilename) + DB.executescript( + """ + CREATE TABLE IF NOT EXISTS call_sites ( + function_name TEXT PRIMARY KEY, + count INT DEFAULT 1 + ); + + CREATE TABLE IF NOT EXISTS call_site_arguments ( + function_name TEXT NOT NULL REFERENCES call_sites(function_name), + argument_name TEXT NOT NULL, + count INT DEFAULT 1, + loc_count INT DEFAULT 0, + PRIMARY KEY (function_name, argument_name) + ); + + CREATE TABLE IF NOT EXISTS inlined_subprograms ( + function_name TEXT PRIMARY KEY, + count INT DEFAULT 1 + ); + + CREATE TABLE IF NOT EXISTS inlined_subprogram_arguments ( + function_name TEXT NOT NULL REFERENCES inlined_subprograms(function_name), + argument_name TEXT NOT NULL, + count INT DEFAULT 1, + loc_count INT DEFAULT 0, + PRIMARY KEY (function_name, argument_name) + ); + """ + ) + + if clear_db: + DB.executescript( + """ + DELETE FROM inlined_subprogram_arguments; + DELETE FROM inlined_subprograms; + DELETE FROM call_site_arguments; + DELETE FROM call_sites; + """ + ) + + return DB + + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def process_file(filename): + eprint("Processing file: ", filename) + with open(filename, "rb") as f: + elffile = ELFFile(f) + + if not elffile.has_dwarf_info(): + eprint(" file has no DWARF info") + return + + dwarfinfo = elffile.get_dwarf_info() + + gc_counter = GC_COUNTER_INIT + for CU in dwarfinfo._parse_CUs_iter(): + top_DIE = CU.get_top_DIE() + top_DIE_path = Path(top_DIE.get_full_path()).as_posix() + if EXCLUDE_PATTERN.search(top_DIE_path): + eprint("Skipping", top_DIE_path, top_DIE.tag, top_DIE.offset) + continue + else: + eprint(top_DIE_path, top_DIE.tag, top_DIE.offset, CU.cu_offset) + + process_die_rec(CU.get_top_DIE()) + + # Clear cache when counter reaches 0, in an attempt to limit memory usage + gc_counter -= 1 + if gc_counter <= 0: + gc_counter = GC_COUNTER_INIT + sys.stderr.flush() + DB.commit() + dwarfinfo._cu_offsets_map.clear() + dwarfinfo._cu_cache.clear() + gc.collect() + + +def process_die_rec(die): + # Start by listing all subroutines we find + if die.tag == "DW_TAG_subprogram": + process_subprogram(die) + else: + for child in die.iter_children(): + process_die_rec(child) + + +def process_subprogram(die): + # We can have subprograms that contains only a typedef + # This might occur when the typedef is within a subprogram + # What's weird is I haven't seen a reference to the original subprogram + if not "DW_AT_name" in die.attributes: + return + + # eprint("Subprogram: ", get_name(die)) + for child in die.iter_children(): + process_subprogram_rec(child) + + +def process_subprogram_rec(die): + # Look for DW_TAG_inlined_subroutine and DW_TAG_GNU_call_site + if die.tag == "DW_TAG_inlined_subroutine": + process_inlined_subroutine(die) + elif die.tag == "DW_TAG_GNU_call_site": + process_call_site(die) + else: + for child in die.iter_children(): + process_subprogram_rec(child) + + +def collect_arguments(function_name, function_die, table): + """ + Insert in the database all the arguments of the given function. + We might not know that some argument are missing if they never appear in + an inlined subprogram or call site. By collecting all the arguments, we + make sure we are aware of this blind spot. + """ + for child in function_die.iter_children(): + aname = None + if child.tag == "DW_TAG_formal_parameter": + aname = get_name(child) + elif child.tag == "DW_TAG_GNU_call_site_parameter": + aname = get_name(child) + else: + continue + + DB.execute( + f"INSERT INTO {table} VALUES (?, ?, 0, 0) ON CONFLICT(function_name, argument_name) DO NOTHING", + (function_name, aname), + ) + + +def process_inlined_subroutine(die): + fname = get_name(die) + DB.execute( + """ + INSERT INTO inlined_subprograms VALUES (?, 1) + ON CONFLICT(function_name) DO + UPDATE SET count = count + 1 + """, + (fname,), + ) + + if "DW_AT_abstract_origin" in die.attributes: + collect_arguments( + fname, + die.get_DIE_from_attribute("DW_AT_abstract_origin"), + "inlined_subprogram_arguments", + ) + + for child in die.iter_children(): + if child.tag == "DW_TAG_inlined_subroutine": + process_inlined_subroutine(child) + elif child.tag == "DW_TAG_formal_parameter": + aname = get_name(child) + DB.execute( + """ + INSERT INTO inlined_subprogram_arguments VALUES (?, ?, 1, ?) + ON CONFLICT(function_name, argument_name) DO UPDATE SET + count = count + 1, + loc_count = loc_count + excluded.loc_count + """, + (fname, aname, int("DW_AT_location" in child.attributes)), + ) + + +def process_call_site(die): + fname = get_name(die) + + DB.execute( + """ + INSERT INTO call_sites VALUES (?, 1) + ON CONFLICT(function_name) DO + UPDATE SET count = count + 1 + """, + (fname,), + ) + + if "DW_AT_abstract_origin" in die.attributes: + collect_arguments( + fname, + die.get_DIE_from_attribute("DW_AT_abstract_origin"), + "call_site_arguments", + ) + + for child in die.iter_children(): + if child.tag == "DW_TAG_inlined_subroutine": + process_inlined_subroutine(child) + elif child.tag == "DW_TAG_formal_parameter": + aname = get_name(child) + DB.execute( + """ + INSERT INTO call_site_arguments VALUES (?, ?, 1, ?) + ON CONFLICT(function_name, argument_name) DO UPDATE SET + count = count + 1, + loc_count = loc_count + excluded.loc_count + """, + (fname, aname, int("DW_AT_location" in child.attributes)), + ) + elif child.tag == "DW_TAG_GNU_call_site_parameter": + aname = get_name(child) + DB.execute( + """ + INSERT INTO call_site_arguments VALUES (?, ?, 1, ?) + ON CONFLICT(function_name, argument_name) DO UPDATE SET + count = count + 1, + loc_count = loc_count + excluded.loc_count + """, + (fname, aname, int("DW_AT_location" in child.attributes)), + ) + + +def print_die_attributes(die): + for name, attr in die.attributes.items(): + eprint(" %s = %s" % (name, attr.value)) + + +def get_name(die): + # We work mainly with mangled C++ name, so look for the linkage name first + if "DW_AT_linkage_name" in die.attributes: + return bytes2str(die.attributes["DW_AT_linkage_name"].value) + elif "DW_AT_name" in die.attributes: + return bytes2str(die.attributes["DW_AT_name"].value) + elif "DW_AT_abstract_origin" in die.attributes: + return get_name(die.get_DIE_from_attribute("DW_AT_abstract_origin")) + elif "DW_AT_specification" in die.attributes: + return get_name(die.get_DIE_from_attribute("DW_AT_specification")) + elif "DW_AT_type" in die.attributes: + return get_name(die.get_DIE_from_attribute("DW_AT_type")) + else: + # print_die_attributes(die) + return "" + + +if __name__ == "__main__": + cli() diff --git a/types/array_type.toml b/types/array_type.toml new file mode 100644 index 0000000..6d91291 --- /dev/null +++ b/types/array_type.toml @@ -0,0 +1,30 @@ +[info] +typeName = "std::array<" +numTemplateParams = 1 +ctype = "ARRAY_TYPE" +header = "array" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)container.size()); + SAVE_SIZE(sizeof(container)); + + for (auto & it: container) { + // undo the static size that has already been added per-element + SAVE_SIZE(-sizeof(it)); + getSizeType(it, returnArg); + } +} +""" diff --git a/types/boost_bimap_type.toml b/types/boost_bimap_type.toml new file mode 100644 index 0000000..6f1f937 --- /dev/null +++ b/types/boost_bimap_type.toml @@ -0,0 +1,33 @@ +[info] +typeName = "boost::bimap" +numTemplateParams = 2 +ctype = "BOOST_BIMAP_TYPE" +header = "boost/bimap.hpp" +ns = ["boost::bimap"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, + size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, + size_t& returnArg) +{ + + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)container.size()); + + for (auto const& it: container) { + getSizeType(it.left, returnArg); + getSizeType(it.right, returnArg); + } +} +""" diff --git a/types/caffe2_blob_type.toml b/types/caffe2_blob_type.toml new file mode 100644 index 0000000..283c18b --- /dev/null +++ b/types/caffe2_blob_type.toml @@ -0,0 +1,92 @@ +[info] +typeName = "caffe2::Blob" +numTemplateParams = 0 +ctype = "CAFFE2_BLOB_TYPE" +header = "caffe2/core/blob.h" +ns = ["caffe2::Blob", "caffe2::Tensor"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +size_t getTensorItemSize(const Tensor& t, size_t& returnArg) { + const caffe2::TypeMeta& tm = t.dtype(); + if (!tm.isScalarType()) { + // For non-scalar types, the pre-defined static array is not accessed + return tm.itemsize(); + } else { + // For scalar types, static array is accessed which does not undergo + // relocattion and causes segfaults. Would be nice to have a better + // way to do this. + + if (tm.Match()) { + return sizeof(uint8_t); + } else if (tm.Match()) { + return sizeof(int8_t); + } else if (tm.Match()) { + return sizeof(uint16_t); + } else if (tm.Match()) { + return sizeof(int); + } else if (tm.Match()) { + return sizeof(int64_t); + } else if (tm.Match()) { + return sizeof(at::Half); + } else if (tm.Match()) { + return sizeof(float); + } else if (tm.Match()) { + return sizeof(double); + } else if (tm.Match>()) { + return sizeof(c10::complex); + } else if (tm.Match>()) { + return sizeof(c10::complex); + } else if (tm.Match>()) { + return sizeof(c10::complex); + } else if (tm.Match()) { + return sizeof(bool); + } else if (tm.Match()) { + return sizeof(c10::qint8); + } else if (tm.Match()) { + return sizeof(c10::quint8); + } else if (tm.Match()) { + return sizeof(c10::qint32); + } else if (tm.Match()) { + return sizeof(at::BFloat16); + } else if (tm.Match()) { + return sizeof(c10::quint4x2); + } + } + return 0; +} + +size_t getTensorSize(const Tensor& t, size_t& returnArg) { + size_t p1 = t.numel(); + size_t p2 = getTensorItemSize(t, returnArg); + return p1 * p2; +} + +void getSizeType(const %1% &blob, size_t& returnArg) { + // TODO: We should have a way to assert at compile time if Blob struct changes + // TODO: Log the type of Blob i.e. Tensor/string + // TODO: Capture the capacity if blob is of string type + if (blob.IsType()) { + const auto& tensor = blob.Get(); + // tensor.nbytes() cannot be directly called since it accesses a static + // array + SAVE_DATA((uintptr_t)getTensorSize(tensor)); + } else if (blob.IsType()) { + const auto& s = blob.Get(); + SAVE_DATA((uintptr_t)s.size()); + /* + } else if (blob.IsType()) { + SAVE_DATA((uintptr_t)sizeof(caffe2::db::DBReader)); + */ + } else { + SAVE_DATA((uintptr_t)0); + } +} +""" diff --git a/types/cxx11_list_type.toml b/types/cxx11_list_type.toml new file mode 100644 index 0000000..2de1520 --- /dev/null +++ b/types/cxx11_list_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::__cxx11::list" +numTemplateParams = 1 +ctype = "LIST_TYPE" +header = "list" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +allocatorIndex = 1 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/cxx11_string_type.toml b/types/cxx11_string_type.toml new file mode 100644 index 0000000..8af41de --- /dev/null +++ b/types/cxx11_string_type.toml @@ -0,0 +1,35 @@ +[info] +typeName = "std::__cxx11::basic_string<" +numTemplateParams = 1 +ctype = "STRING_TYPE" +header = "string" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &t, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &t, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)t.capacity()); + SAVE_DATA((uintptr_t)t.size()); + + // Test for small string optimisation - whether the underlying string is + // contained within the string object. + SAVE_SIZE( + (((uintptr_t)t.data() < (uintptr_t)(&t + sizeof(%1%))) + && + ((uintptr_t)t.data() >= (uintptr_t)&t)) + ? 0 : (t.capacity() * sizeof(T)) + ); +} +""" diff --git a/types/deque_list_type.toml b/types/deque_list_type.toml new file mode 100644 index 0000000..7691e7f --- /dev/null +++ b/types/deque_list_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::deque<" +numTemplateParams = 1 +ctype = "LIST_TYPE" +header = "deque" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +allocatorIndex = 1 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/f14_fast_map.toml b/types/f14_fast_map.toml new file mode 100644 index 0000000..c902271 --- /dev/null +++ b/types/f14_fast_map.toml @@ -0,0 +1,34 @@ +[info] +typeName = "folly::F14FastMap<" +numTemplateParams = 2 +ctype = "F14_MAP" +header = "folly/container/F14Map.h" +ns = ["folly::F14FastMap"] +replaceTemplateParamIndex = [2, 3] +allocatorIndex = 4 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& dataSegOffset); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/f14_fast_set.toml b/types/f14_fast_set.toml new file mode 100644 index 0000000..8b6e5b6 --- /dev/null +++ b/types/f14_fast_set.toml @@ -0,0 +1,33 @@ +[info] +typeName = "folly::F14FastSet<" +numTemplateParams = 1 +ctype = "F14_SET" +header = "folly/container/F14Set.h" +ns = ["folly::F14FastSet"] +replaceTemplateParamIndex = [1, 2] +allocatorIndex = 3 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/f14_node_map.toml b/types/f14_node_map.toml new file mode 100644 index 0000000..8bf6469 --- /dev/null +++ b/types/f14_node_map.toml @@ -0,0 +1,34 @@ +[info] +typeName = "folly::F14NodeMap<" +numTemplateParams = 2 +ctype = "F14_MAP" +header = "folly/container/F14Map.h" +ns = ["folly::F14NodeMap"] +replaceTemplateParamIndex = [2, 3] +allocatorIndex = 4 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& dataSegOffset); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/f14_node_set.toml b/types/f14_node_set.toml new file mode 100644 index 0000000..0783adc --- /dev/null +++ b/types/f14_node_set.toml @@ -0,0 +1,33 @@ +[info] +typeName = "folly::F14NodeSet<" +numTemplateParams = 1 +ctype = "F14_SET" +header = "folly/container/detail/F14SetFallback.h" +ns = ["folly::F14NodeSet"] +replaceTemplateParamIndex = [1, 2] +allocatorIndex = 3 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/f14_vector_map.toml b/types/f14_vector_map.toml new file mode 100644 index 0000000..5e4a831 --- /dev/null +++ b/types/f14_vector_map.toml @@ -0,0 +1,34 @@ +[info] +typeName = "folly::F14VectorMap<" +numTemplateParams = 2 +ctype = "F14_MAP" +header = "folly/container/detail/F14MapFallback.h" +ns = ["folly::F14VectorMap"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& dataSegOffset); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/f14_vector_set.toml b/types/f14_vector_set.toml new file mode 100644 index 0000000..a0bdf5f --- /dev/null +++ b/types/f14_vector_set.toml @@ -0,0 +1,33 @@ +[info] +typeName = "folly::F14VectorSet<" +numTemplateParams = 1 +ctype = "F14_SET" +header = "folly/container/F14Set.h" +ns = ["folly::F14VectorSet"] +replaceTemplateParamIndex = [1, 2] +allocatorIndex = 3 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + size_t memorySize = container.getAllocatedMemorySize(); + SAVE_SIZE(sizeof(%1%) + memorySize); + + SAVE_DATA(memorySize); + SAVE_DATA(container.bucket_count()); + SAVE_DATA(container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/fb_string_type.toml b/types/fb_string_type.toml new file mode 100644 index 0000000..6450f3d --- /dev/null +++ b/types/fb_string_type.toml @@ -0,0 +1,25 @@ +[info] +typeName = "folly::basic_fbstring<" +numTemplateParams = 1 +ctype = "FB_STRING_TYPE" +header = "folly/FBString.h" +ns = ["folly::basic_fbstring"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &t, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &t, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)(t.data())); + SAVE_DATA((uintptr_t)t.capacity()); + SAVE_DATA((uintptr_t)t.size()); +} +""" diff --git a/types/folly_iobuf_queue_type.toml b/types/folly_iobuf_queue_type.toml new file mode 100644 index 0000000..799dd05 --- /dev/null +++ b/types/folly_iobuf_queue_type.toml @@ -0,0 +1,26 @@ +[info] +typeName = "folly::IOBufQueue" +numTemplateParams = 0 +ctype = "FOLLY_IOBUFQUEUE_TYPE" +header = "folly/io/IOBufQueue.h" +ns = ["folly::IOBufQueue"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +void getSizeType(const %1% &t, size_t& returnArg); +""" + +func = """ +void getSizeType(const %1% &t, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + const IOBuf *head = t.front(); + SAVE_DATA((uintptr_t)head); + if (head) + getSizeType(*head, returnArg); +} +""" diff --git a/types/folly_iobuf_type.toml b/types/folly_iobuf_type.toml new file mode 100644 index 0000000..f463add --- /dev/null +++ b/types/folly_iobuf_type.toml @@ -0,0 +1,39 @@ +[info] +typeName = "folly::IOBuf" +numTemplateParams = 0 +ctype = "FOLLY_IOBUF_TYPE" +header = "folly/io/IOBuf.h" +ns = ["folly::IOBuf"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +void getSizeType(const %1% &t, size_t& returnArg); +""" + +func = """ +void getSizeType(const %1% &t, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + // We calculate the length of all IOBufs in the chain manually. + // IOBuf has built-in computeChainCapacity()/computeChainLength() + // functions which do this for us. But dead code optimization in TAO + // caused these functions to be removed which causes relocation + // errors. + + std::size_t fullLength = t.length(); + std::size_t fullCapacity = t.capacity(); + for (const IOBuf* current = t.next(); current != &t; + current = current->next()) { + fullLength += current->length(); + fullCapacity += current->capacity(); + } + SAVE_DATA(fullCapacity); + SAVE_DATA(fullLength); + + SAVE_SIZE(fullCapacity); +} +""" diff --git a/types/folly_optional_type.toml b/types/folly_optional_type.toml new file mode 100644 index 0000000..982bf7a --- /dev/null +++ b/types/folly_optional_type.toml @@ -0,0 +1,30 @@ +[info] +typeName = "folly::Optional<" +numTemplateParams = 1 +ctype = "FOLLY_OPTIONAL_TYPE" +header = "folly/Optional.h" +ns = ["folly::Optional"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1%& s_ptr, size_t& returnArg) { + if (s_ptr) { + SAVE_SIZE(sizeof(%1%) - sizeof(T)); + SAVE_DATA((uintptr_t)(s_ptr.get_pointer())); + + getSizeType(*(s_ptr.get_pointer()), returnArg); + } else { + SAVE_SIZE(sizeof(%1%)); + SAVE_DATA(0); + } +} +""" diff --git a/types/folly_small_heap_vector_map.toml b/types/folly_small_heap_vector_map.toml new file mode 100644 index 0000000..f147bc5 --- /dev/null +++ b/types/folly_small_heap_vector_map.toml @@ -0,0 +1,35 @@ +[info] +typeName = "folly::small_heap_vector_map" +numTemplateParams = 2 +ctype = "FOLLY_SMALL_HEAP_VECTOR_MAP" +header = "folly/container/heap_vector_types.h" +ns = ["folly::small_heap_vector_map"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.capacity()); + SAVE_DATA((uintptr_t)container.size()); + + SAVE_SIZE((container.capacity() - container.size()) * (sizeof(Key) + sizeof(Value))); + + for (auto const& it : container) + { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/list_type.toml b/types/list_type.toml new file mode 100644 index 0000000..ef7df87 --- /dev/null +++ b/types/list_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::list<" +numTemplateParams = 1 +ctype = "LIST_TYPE" +header = "list" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +allocatorIndex = 1 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.size()); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/map_seq_type.toml b/types/map_seq_type.toml new file mode 100644 index 0000000..50fef49 --- /dev/null +++ b/types/map_seq_type.toml @@ -0,0 +1,35 @@ +[info] +typeName = "folly::sorted_vector_map<" +numTemplateParams = 2 +ctype = "MAP_SEQ_TYPE" +header = "folly/sorted_vector_types.h" +ns = ["namespace std", "folly::sorted_vector_map"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.capacity()); + SAVE_DATA((uintptr_t)container.size()); + + SAVE_SIZE((container.capacity() - container.size()) * (sizeof(Key) + sizeof(Value))); + + for (auto const& it : container) + { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/multi_map_type.toml b/types/multi_map_type.toml new file mode 100644 index 0000000..9e66bd1 --- /dev/null +++ b/types/multi_map_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::multimap" +numTemplateParams = 2 +ctype = "MULTI_MAP_TYPE" +header = "map" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)container.size()); + + for (auto const& it : container) + { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/optional_type.toml b/types/optional_type.toml new file mode 100644 index 0000000..5c3547a --- /dev/null +++ b/types/optional_type.toml @@ -0,0 +1,29 @@ +[info] +typeName = "std::optional<" +numTemplateParams = 1 +ctype = "OPTIONAL_TYPE" +header = "optional" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1%& s_ptr, size_t& returnArg) { + if (s_ptr) { + SAVE_SIZE(sizeof(%1%) - sizeof(T)); + SAVE_DATA(true); + getSizeType(*s_ptr, returnArg); + } else { + SAVE_SIZE(sizeof(%1%)); + SAVE_DATA(false); + } +} +""" diff --git a/types/pair_type.toml b/types/pair_type.toml new file mode 100644 index 0000000..6f106ee --- /dev/null +++ b/types/pair_type.toml @@ -0,0 +1,26 @@ +[info] +typeName = "std::pair<" +numTemplateParams = 2 +ctype = "PAIR_TYPE" +header = "utility" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &p, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &p, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%) - sizeof(P) - sizeof(Q)); + + getSizeType(p.first, returnArg); + getSizeType(p.second, returnArg); +} +""" diff --git a/types/priority_queue_container_adapter_type.toml b/types/priority_queue_container_adapter_type.toml new file mode 100644 index 0000000..82520aa --- /dev/null +++ b/types/priority_queue_container_adapter_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::priority_queue<" +# numTemplateParams = 1 +ctype = "CONTAINER_ADAPTER_TYPE" +header = "queue" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +underlyingContainerIndex = 1 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &containerAdapter, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)&containerAdapter); + + // Only record the overhead of this container adapter - don't count the + // underlying container as that will be taken care of in its own + // getSizeType() function + SAVE_SIZE(sizeof(%1%) - sizeof(Container)); + + const Container &container = get_container(containerAdapter); + getSizeType(container, returnArg); +} +""" diff --git a/types/queue_container_adapter_type.toml b/types/queue_container_adapter_type.toml new file mode 100644 index 0000000..63e794f --- /dev/null +++ b/types/queue_container_adapter_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::queue<" +# numTemplateParams = 1 +ctype = "CONTAINER_ADAPTER_TYPE" +header = "queue" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +underlyingContainerIndex = 1 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &containerAdapter, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)&containerAdapter); + + // Only record the overhead of this container adapter - don't count the + // underlying container as that will be taken care of in its own + // getSizeType() function + SAVE_SIZE(sizeof(%1%) - sizeof(Container)); + + const Container &container = get_container(containerAdapter); + getSizeType(container, returnArg); +} +""" diff --git a/types/ref_wrapper_type.toml b/types/ref_wrapper_type.toml new file mode 100644 index 0000000..2ef6026 --- /dev/null +++ b/types/ref_wrapper_type.toml @@ -0,0 +1,25 @@ +[info] +typeName = "std::reference_wrapper<" +numTemplateParams = 1 +ctype = "REF_WRAPPER_TYPE" +header = "functional" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &ref, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + SAVE_DATA((uintptr_t)&(ref.get())); + getSizeType(ref.get(), returnArg); +} +""" diff --git a/types/repeated_field_type.toml b/types/repeated_field_type.toml new file mode 100644 index 0000000..3bc847e --- /dev/null +++ b/types/repeated_field_type.toml @@ -0,0 +1,29 @@ +[info] +typeName = "google::protobuf::RepeatedField<" +numTemplateParams = 1 +ctype = "REPEATED_FIELD_TYPE" +header = "google/protobuf/repeated_field.h" +ns = ["google::protobuf::RepeatedField"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.Capacity()); + SAVE_DATA((uintptr_t)container.size()); + // The double ampersand is needed otherwise this loop doesn't work with vector + for (const auto& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/repeated_ptr_field_type.toml b/types/repeated_ptr_field_type.toml new file mode 100644 index 0000000..d4947e1 --- /dev/null +++ b/types/repeated_ptr_field_type.toml @@ -0,0 +1,29 @@ +[info] +typeName = "google::protobuf::RepeatedPtrField<" +numTemplateParams = 1 +ctype = "REPEATED_FIELD_TYPE" +header = "google/protobuf/repeated_field.h" +ns = ["google::protobuf::RepeatedPtrField"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.Capacity()); + SAVE_DATA((uintptr_t)container.size()); + // The double ampersand is needed otherwise this loop doesn't work with vector + for (const auto& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/seq_type.toml b/types/seq_type.toml new file mode 100644 index 0000000..cc6646a --- /dev/null +++ b/types/seq_type.toml @@ -0,0 +1,34 @@ +[info] +typeName = "std::vector<" +numTemplateParams = 1 +ctype = "SEQ_TYPE" +header = "vector" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +allocatorIndex = 1 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.capacity()); + SAVE_DATA((uintptr_t)container.size()); + + SAVE_SIZE((container.capacity() - container.size()) * sizeof(T)); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/set_type.toml b/types/set_type.toml new file mode 100644 index 0000000..9b8e434 --- /dev/null +++ b/types/set_type.toml @@ -0,0 +1,34 @@ +[info] +typeName = "std::set<" +numTemplateParams = 1 +ctype = "SET_TYPE" +header = "set" +ns = ["namespace std"] +replaceTemplateParamIndex = [1] +allocatorIndex = 2 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + constexpr size_t nodeSize = sizeof(typename %1%::node_type); + size_t numElems = container.size(); + + SAVE_SIZE(sizeof(%1%) + (nodeSize * numElems)); + + SAVE_DATA((uintptr_t)nodeSize); + SAVE_DATA((uintptr_t)numElems); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/shrd_ptr_type.toml b/types/shrd_ptr_type.toml new file mode 100644 index 0000000..85437fa --- /dev/null +++ b/types/shrd_ptr_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::shared_ptr" +numTemplateParams = 1 +ctype = "SHRD_PTR_TYPE" +header = "memory" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + if constexpr (!std::is_void::value) { + SAVE_DATA((uintptr_t)(s_ptr.get())); + + if (s_ptr && pointers.add((uintptr_t)(s_ptr.get()))) { + getSizeType(*(s_ptr.get()), returnArg); + } + } +} +""" diff --git a/types/small_vec_type.toml b/types/small_vec_type.toml new file mode 100644 index 0000000..309c700 --- /dev/null +++ b/types/small_vec_type.toml @@ -0,0 +1,30 @@ +[info] +typeName = "folly::small_vector<" +numTemplateParams = 1 +ctype = "SMALL_VEC_TYPE" +header = "folly/small_vector.h" +ns = ["folly::small_vector"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + SAVE_DATA((uintptr_t)(N)); + SAVE_DATA((uintptr_t)container.capacity()); + SAVE_DATA((uintptr_t)container.size()); + + for (auto & it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/sorted_vec_set_type.toml b/types/sorted_vec_set_type.toml new file mode 100644 index 0000000..c807ffb --- /dev/null +++ b/types/sorted_vec_set_type.toml @@ -0,0 +1,34 @@ +[info] +typeName = "folly::sorted_vector_set<" +numTemplateParams = 1 +ctype = "SORTED_VEC_SET_TYPE" +header = "folly/sorted_vector_types.h" +ns = ["namespace std", "folly::sorted_vector_set"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)&container); + SAVE_DATA((uintptr_t)container.capacity()); + SAVE_DATA((uintptr_t)container.size()); + + SAVE_SIZE((container.capacity() - container.size()) * sizeof(T)); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/types/stack_container_adapter_type.toml b/types/stack_container_adapter_type.toml new file mode 100644 index 0000000..77a8fdd --- /dev/null +++ b/types/stack_container_adapter_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::stack<" +# numTemplateParams = 1 +ctype = "CONTAINER_ADAPTER_TYPE" +header = "stack" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +underlyingContainerIndex = 1 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &containerAdapter, size_t& returnArg) +{ + SAVE_DATA((uintptr_t)&containerAdapter); + + // Only record the overhead of this container adapter - don't count the + // underlying container as that will be taken care of in its own + // getSizeType() function + SAVE_SIZE(sizeof(%1%) - sizeof(Container)); + + const Container &container = get_container(containerAdapter); + getSizeType(container, returnArg); +} +""" diff --git a/types/std_map_type.toml b/types/std_map_type.toml new file mode 100644 index 0000000..4ab538b --- /dev/null +++ b/types/std_map_type.toml @@ -0,0 +1,35 @@ +[info] +typeName = "std::map<" +numTemplateParams = 2 +ctype = "STD_MAP_TYPE" +header = "map" +ns = ["namespace std"] +replaceTemplateParamIndex = [2] +allocatorIndex = 3 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + constexpr size_t nodeSize = sizeof(typename %1%::node_type); + size_t numElems = container.size(); + + SAVE_SIZE(sizeof(%1%) + (nodeSize * numElems)); + + SAVE_DATA((uintptr_t)nodeSize); + SAVE_DATA((uintptr_t)numElems); + + for (auto const& it : container) + { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/std_unordered_map_type.toml b/types/std_unordered_map_type.toml new file mode 100644 index 0000000..cf9cb10 --- /dev/null +++ b/types/std_unordered_map_type.toml @@ -0,0 +1,37 @@ +[info] +typeName = "std::unordered_map<" +numTemplateParams = 2 +ctype = "STD_UNORDERED_MAP_TYPE" +header = "unordered_map" +ns = ["namespace std"] +replaceTemplateParamIndex = [2, 3] +allocatorIndex = 4 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + constexpr size_t nodeSize = sizeof(typename %1%::node_type); + size_t bucketCount = container.bucket_count(); + size_t numElems = container.size(); + + SAVE_SIZE(sizeof(%1%) + (nodeSize * numElems) + (bucketCount * sizeof(uintptr_t))); + + SAVE_DATA((uintptr_t)nodeSize); + SAVE_DATA((uintptr_t)bucketCount); + SAVE_DATA((uintptr_t)numElems); + + for (auto const& it : container) + { + getSizeType(it.first, returnArg); + getSizeType(it.second, returnArg); + } +} +""" diff --git a/types/std_variant.toml b/types/std_variant.toml new file mode 100644 index 0000000..c9c72ef --- /dev/null +++ b/types/std_variant.toml @@ -0,0 +1,42 @@ +[info] +typeName = "std::variant" +ctype = "STD_VARIANT_TYPE" +header = "variant" +ns = ["namespace std"] + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeVariantContents(const %1% &container, size_t& returnArg) +{ + if constexpr (I < sizeof...(Types)) + { + if (I == container.index()) + { + // Contents are stored inline - don't double count + SAVE_SIZE(-sizeof(std::get(container))); + + getSizeType(std::get(container), returnArg); + } + else + { + getSizeVariantContents(container, returnArg); + } + } + // else variant is valueless - save no data +} + +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA(container.index()); + getSizeVariantContents(container, returnArg); +} +""" diff --git a/types/string_type.toml b/types/string_type.toml new file mode 100644 index 0000000..fb48c1b --- /dev/null +++ b/types/string_type.toml @@ -0,0 +1,35 @@ +[info] +typeName = "std::basic_string<" +numTemplateParams = 1 +ctype = "STRING_TYPE" +header = "string" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &t, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &t, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + SAVE_DATA((uintptr_t)t.capacity()); + SAVE_DATA((uintptr_t)t.size()); + + // Test for small string optimisation - whether the underlying string is + // contained within the string object. + SAVE_SIZE( + ((uintptr_t)t.data() < (uintptr_t)(&t + sizeof(%1%))) + && + ((uintptr_t)t.data() >= (uintptr_t)&t) + ? 0 : (t.capacity() * sizeof(T)) + ); +} +""" diff --git a/types/thrift_isset_type.toml b/types/thrift_isset_type.toml new file mode 100644 index 0000000..95af9db --- /dev/null +++ b/types/thrift_isset_type.toml @@ -0,0 +1,17 @@ +[info] +typeName = "apache::thrift::detail::isset_bitset<" +numTemplateParams = 2 +ctype = "THRIFT_ISSET_TYPE" +header = "thrift/lib/cpp2/gen/module_types_h.h" +ns = ["apache::thrift::detail::isset_bitset"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +// DummyDecl %1% +""" +func = """ +// DummyFunc %1% +""" diff --git a/types/try_type.toml b/types/try_type.toml new file mode 100644 index 0000000..fb007ae --- /dev/null +++ b/types/try_type.toml @@ -0,0 +1,29 @@ +[info] +typeName = "folly::Try<" +numTemplateParams = 1 +ctype = "TRY_TYPE" +header = "folly/Try.h" +ns = ["folly::Try"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + if (s_ptr.hasValue()) { + SAVE_DATA((uintptr_t)(&(s_ptr.value()))); + getSizeType(s_ptr.value(), returnArg); + } else { + SAVE_DATA(0); + } +} +""" diff --git a/types/uniq_ptr_type.toml b/types/uniq_ptr_type.toml new file mode 100644 index 0000000..304f799 --- /dev/null +++ b/types/uniq_ptr_type.toml @@ -0,0 +1,31 @@ +[info] +typeName = "std::unique_ptr" +numTemplateParams = 1 +ctype = "UNIQ_PTR_TYPE" +header = "memory" +ns = ["namespace std"] +replaceTemplateParamIndex = [] +# allocatorIndex = 0 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &s_ptr, size_t& returnArg) +{ + SAVE_SIZE(sizeof(%1%)); + + if constexpr (!std::is_void::value) { + SAVE_DATA((uintptr_t)(s_ptr.get())); + + if (s_ptr && pointers.add((uintptr_t)(s_ptr.get()))) { + getSizeType(*(s_ptr.get()), returnArg); + } + } +} +""" diff --git a/types/unordered_set_type.toml b/types/unordered_set_type.toml new file mode 100644 index 0000000..10f2044 --- /dev/null +++ b/types/unordered_set_type.toml @@ -0,0 +1,35 @@ +[info] +typeName = "std::unordered_set<" +numTemplateParams = 1 +ctype = "UNORDERED_SET_TYPE" +header = "unordered_set" +ns = ["namespace std"] +replaceTemplateParamIndex = [1, 2] +allocatorIndex = 3 +# underlyingContainerIndex = 0 + +[codegen] +decl = """ +template +void getSizeType(const %1% &container, size_t& returnArg); +""" + +func = """ +template +void getSizeType(const %1% &container, size_t& returnArg) +{ + constexpr size_t nodeSize = sizeof(typename %1%::node_type); + size_t bucketCount = container.bucket_count(); + size_t numElems = container.size(); + SAVE_SIZE(sizeof(%1%) + (numElems * nodeSize) + (bucketCount * sizeof(uintptr_t))); + + SAVE_DATA((uintptr_t)nodeSize); + SAVE_DATA((uintptr_t)bucketCount); + SAVE_DATA((uintptr_t)numElems); + + // The double ampersand is needed otherwise this loop doesn't work with vector + for (auto&& it: container) { + getSizeType(it, returnArg); + } +} +""" diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..6ffad61 --- /dev/null +++ b/website/README.md @@ -0,0 +1,49 @@ +# Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +``` +$ yarn +``` + +### Local Development + +``` +$ yarn start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +``` +$ yarn build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +### Deployment + +Using SSH: + +``` +$ USE_SSH=true yarn deploy +``` + +Not using SSH: + +``` +$ GIT_USER= yarn deploy +``` + +If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. + +### Continuous Integration + +Some common defaults for linting/formatting have been set for you. If you integrate your project with an open source Continuous Integration system (e.g. Travis CI, CircleCI), you may check for issues using the following command. + +``` +$ yarn ci +``` diff --git a/website/babel.config.js b/website/babel.config.js new file mode 100644 index 0000000..84ad45a --- /dev/null +++ b/website/babel.config.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/website/docs/addrbook-funcargs.md b/website/docs/addrbook-funcargs.md new file mode 100644 index 0000000..b896042 --- /dev/null +++ b/website/docs/addrbook-funcargs.md @@ -0,0 +1,83 @@ +--- +title: Function Arguments +--- + +# Introspecting Function Arguments + +We'll now look at a simple example of introspecting objects upon entry to a function. Adding a contact to the address book takes 3 string objects passed by reference: + +``` + void AddContact(std::string& f, std::string& l, std::string& n); +``` + +We need the symbol name to form the probe specification: + +``` +$ ~/object-introspection/examples/web/AddrBook# readelf -sW addrbook | grep AddContext + 76: 0000000000401980 221 FUNC WEAK DEFAULT 15 _ZN11AddressBook10AddContactERNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES6_S6_ +``` + +So the probe specification for introspecting the first and third argument (FirstName and Number) passed to `AddContact()` is: + +``` +$ ~/object-introspection/examples/web/AddrBook# cat /tmp/addrentry.oid +entry:_ZN11AddressBook10AddContactERNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES6_S6_:arg0,arg2 +``` + +A few small things to note: + +
    +
  • Instead of specifying the probe on the command line with the `-S` option we've put it into a file as longer probe specifications become a bit unwieldy in the command line for some of us!
  • +
  • Arguments to functions are referenced by `argX` where `X` begins at 0 for the first argument.
  • +
  • One or more arguments can be specified in the probe specification.
  • +
+ +``` +$ ~/object-introspection# jq . oid_out.json +[ + { + "name": "f", + "typePath": "f", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 0, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::allocator >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 0, + "length": 14, + "capacity": 15, + "elementStaticSize": 1 + } + ] + }, + { + "name": "n", + "typePath": "n", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 23, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::allocator >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 23, + "length": 23, + "capacity": 23, + "elementStaticSize": 1 + } + ] + } +] +``` + +The above shows us that the first argument, f, is 14 bytes in length and fits within the SSO static buffer while the third argument, n, is 23 characters long and there occupies dynamically allocated memory external the the string object. diff --git a/website/docs/addrbook-intro.md b/website/docs/addrbook-intro.md new file mode 100644 index 0000000..5b55e9d --- /dev/null +++ b/website/docs/addrbook-intro.md @@ -0,0 +1,61 @@ +--- +title: A Simple Address Book Example +--- + +# A simple address book example + +Let's start with a very simple C++ application: an address book. This contrived simple piece of code contains everything we need to take you through the basics of using OI. The code itself can be found in the `examples/web/AddrBook` directory in the OI [GitHub repo](https://github.com/facebookexperimental/object-introspection/tree/main/examples/web/AddrBook). + +First, build the test application: +``` +$ ~/object-introspection/examples/web/AddrBook: make CC=clang++-12 +clang++-12 -o addrbook AddrBook.cpp -std=c++20 -g -O3 +``` + +(No need to override the 'CC' make variable if you have `clang++` in your path). + +You can see the DWARF data is present in the generated executable: + +``` +$ ~/object-introspection/examples/web/AddrBook# size -At addrbook | grep "\.debug" +.debug_info 71316 0 +.debug_abbrev 2446 0 +.debug_line 8971 0 +.debug_str 44990 0 +.debug_loc 27968 0 +.debug_ranges 10240 0 +``` + +Each address book is composed of a single `AddressBook` object which contains zero or more `Contact` objects. Here's how the data and interface definitions look for the two objects: + +```C++ +class Contact { +public: + Contact(std::string& f, std::string& l, std::string& n); +private: + std::string firstName, lastName; + std::string number; +}; + +class AddressBook { +public: + void AddContact(std::string& f, std::string& l, std::string& n); + void DumpContacts(void); +private: + int rev; + std::string Owner; + std::vector Entries; +}; +``` + +OI can introspect objects at specific points in an application: + +
    +
  • Function arguments upon entry to a function.
  • +
  • Function arguments upon return to a function.
  • +
  • The return value from a function.
  • +
  • This `this` pointer at entry or return from an object method.
  • +
  • Global objects.
  • +
+ +Let's get started by introspecting an object using its `this` pointer. diff --git a/website/docs/addrbook-this.md b/website/docs/addrbook-this.md new file mode 100644 index 0000000..ba6012d --- /dev/null +++ b/website/docs/addrbook-this.md @@ -0,0 +1,189 @@ +--- +title: this pointers +--- + +We specify exactly which function and arguments are to be introspected through a ***probe specification*** (terminology borrowed from [DTrace](https://en.wikipedia.org/wiki/DTrace)). It's simply a colon delimited tuple that specifies exactly what object we are interested in and where in the code we want to observe it. For example, to introspect the `AddressBook` object we can measure it at the entry to its `DumpContacts()` method and the specification would be: + +``` + entry:_ZN11AddressBook12DumpContactsEv:this +``` + +There are a few points to note here: + +
    +
  • We are using the AddressBook::DumpContacts method. Note that we specify the function name using the mangled C++ name which can be found using readelf -sW /path/to/binary | grep symbol.
  • +
  • We are introspecting the object itself through the this specifier.
  • +
  • We are introspecting on entry to the method but we could have chosen to introspect the object the object at the return point from the AddressBook::DumpContacts() method which would be useful if state had been altered during execution of he method.
  • +
+ +We use the `oid` debugger to capture the object itself from the address book application. Every second a contact is added to the address book with the `AddContact()` method and the address book is dumped with the `DumpContacts()` method. After running for a minute or so let's capture the Address book object: + +``` +$ build/oid -S 'entry:_ZN11AddressBook12DumpContactsEv:this' -p `pgrep addrbook` -c build/oid-cfg.toml -J +Attached to pid 4039830 +SUCCESS +``` + +The `-J` flag instructed `oid` to dump the captured objects introspection data in a JSON file: + +``` +$ ~/object-introspection# ls -l oid_out.json +-rw-r--r-- 1 root root 78975 Dec 16 20:35 oid_out.json +``` + +This is a simple object but the resulting JSON becomes quite large when more than a handful of contacts have been added. Let's examine some sections to see what we can glean about the object that has been captured: + +``` +$ jq . oid_out.json +[ + { + "name": "this", + "typePath": "this", + "typeName": "AddressBook", + "isTypedef": false, + "staticSize": 64, + "dynamicSize": 23185, + "paddingSavingsSize": 4, + "members": [ +``` + +The root type is an AddressBook object as we'd expect and it has a static footprint of 64 bytes with its data members in total having a dynamic memory footprint of 23185 bytes. + +The first member is simply an integer for the rev data member which has a 4 byte static memory footprint on this platform. + +``` + "members": [ + { + "name": "rev", + "typePath": "rev", + "typeName": "int", + "isTypedef": false, + "staticSize": 4, + "dynamicSize": 0 + }, +``` + +Next we have a std::string object for the top level Owner member. Note how this is expanded down to the base types which show the string has a static footprint of 32 bytes but we can see that it is empty as it has a dynamicSize of 0. The capacity of 15 bytes shows the Short String optimization buffer in a libstdc++ std::string object. + +``` + { + "name": "Owner", + "typePath": "Owner", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 0, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::alloca +tor >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 0, + "length": 0, + "capacity": 15, + "elementStaticSize": 1 + } + ] + }, +``` + +The Entries member gets a bit more interesting as it introduces usage of a C++ container class, the std::vector. More accurately, it is a vector of Contact objects as can be seen in the typeName JSON member for the root of the Entries object shown below. Note that the vector currently has an allocated capacity of 128 Contact elements of which 71 are actual Contact objects. The 23185 bytes of dynamically allocated objects in the vector is composed of the 71 x 96 byte Contact objects plus whatever memory that has been dynamically allocated for string content in those objects: +``` + { + "name": "Entries", + "typePath": "Entries", + "typeName": "vector >", + "isTypedef": false, + "staticSize": 24, + "dynamicSize": 23185, + "pointer": 140720550450568, + "length": 71, + "capacity": 128, + "elementStaticSize": 96, +``` + +For the sake of brevity let's just inspect the first `Contact` object shown below. It's static footprint at 96 bytes is the three strings at 32 bytes each. We can see that the first and third string have strings larger than the 15 bytes allocated for the short string optimization buffer and are therefore allocated in dynamic memory that is external to the string objects themselves. The second string has no dynamic memory allocated as the 14 byte character sequence it is housing fits within the 15 byte pre-allocated buffer: +``` + + "members": [ + { + "name": "", + "typePath": "Contact[]", + "typeName": "Contact", + "isTypedef": false, + "staticSize": 96, + "dynamicSize": 65, + "members": [ + { + "name": "firstName", + "typePath": "firstName", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 35, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::allocator >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 35, + "length": 35, + "capacity": 35, + "elementStaticSize": 1 + } + ] + }, + { + "name": "lastName", + "typePath": "lastName", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 0, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::allocator >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 0, + "length": 14, + "capacity": 15, + "elementStaticSize": 1 + } + ] + }, + { + "name": "number", + "typePath": "number", + "typeName": "string", + "isTypedef": true, + "staticSize": 32, + "dynamicSize": 30, + "members": [ + { + "name": "", + "typePath": "", + "typeName": "basic_string, std::allocator >", + "isTypedef": false, + "staticSize": 32, + "dynamicSize": 30, + "length": 18, + "capacity": 30, + "elementStaticSize": 1 + } + ] + } +``` + +In total we have 71 `Contact` objects introspected here and each can be analyzed as we have done above: +``` +$ ~/object-introspection# jq . /tmp/oit.oit.json.fmt | grep -c "\"typeName\": \"Contact\"" +71 +``` diff --git a/website/docs/constraints.md b/website/docs/constraints.md new file mode 100644 index 0000000..c034eb5 --- /dev/null +++ b/website/docs/constraints.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 3 +--- + +# Limitations and Constraints + +OI has been initially designed for use within Meta and therefore some of its current implementation may present challenges for you in your environment. We'd love to hear from you about what you need supporting and how any limitations effect you so please feel free to create issues on [GitHub](https://github.com/facebookexperimental/object-introspection) or tell us directly on [Matrix](https://matrix.to/#/#object-introspection:matrix.org) or [IRC](irc://irc.oftc.net/#object-introspection). + +Current known limitations and constraints: + +
    +
  • Statically linked binaries only.
  • +
  • Split Debug DWARF not currently supported (planned).
  • +
  • C style unions not supported.
  • +
  • Only supported architecture is x86-64.
  • +
  • Only single probe specifications allowed in an oid invocation
  • +
  • Virtual inheritance support
  • +
  • Template specialization support
  • +
  • Pluggable container support
  • +
diff --git a/website/docs/contributing.md b/website/docs/contributing.md new file mode 100644 index 0000000..e120402 --- /dev/null +++ b/website/docs/contributing.md @@ -0,0 +1,6 @@ +--- +--- + +# Contributing + +We welcome contributions from the community! If you're looking for an idea then feel free to pick one of the open issues on GitHub. Feel free to drop by the object-introspection chat rooms ([Matrix](https://matrix.to/#/#object-introspection:matrix.org)/[IRC](irc://irc.oftc.net/#object-introspection)). diff --git a/website/docs/getting-started.md b/website/docs/getting-started.md new file mode 100644 index 0000000..134a14d --- /dev/null +++ b/website/docs/getting-started.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 2 +--- + +# Getting Started + +## Dependencies + +First ensure your system has all the dependencies installed. We have tested on Ubuntu 22.04.01 (Jammy) OpenSuse and Fedora 37 and the dependencies are listed below. Please let us know if you have issues with these or any other Linux repos and we'll do our best to look into it. + +### Ubuntu 22.04.1 (Jammy) + +``` +sudo apt-get update +sudo apt-get install -y bison autopoint build-essential clang-12 cmake flex gawk libboost-all-dev libbz2-dev libcap2-bin libclang-12-dev libcurl4-gnutls-dev libdouble-conversion-dev libdw-dev libfmt-dev libgflags-dev libgmock-dev libgoogle-glog-dev libgtest-dev libjemalloc-dev libmsgpack-dev libzstd-dev llvm-12-dev ninja-build pkg-config python3-setuptools sudo xsltproc libboost-all-dev +sudo pip3 install toml +``` + +### OpenSuse Tumbleweed + +``` +sudo zypper in git ninja cmake llvm12-devel clang12-devel binutils-gold gcc gcc-c++ libcap-progs sudo gflags-devel-static gflags-devel bison libboost_{system,filesystem,thread,regex,serialization}-devel msgpack-c-devel libzstd-devel flex gtest gmock python3-toml python3-devel python3-setuptools gettext-tools findutils zlib-devel libcurl-devel libbz2-devel libdw-devel libdwarf-devel jemalloc-devel msgpack-cxx-devel double-conversion-devel fmt-devel +sudo apt install libdw-dev libclang-dev llvm-dev libboost-all-dev libgtest-dev libbz2-dev libgflags-dev libzstd-dev libcurl4-gnutls-dev ninja-build python3-toml +``` + +### Fedora 37 + +``` +Package installation instructions here for Fedora 37. + +``` + +### Clone the OI repo: + +``` +$ git clone git@github.com:facebookexperimental/object-introspection.git +Cloning into 'object-introspection'... +remote: Enumerating objects: 8694, done. +remote: Counting objects: 100% (116/116), done. +remote: Compressing objects: 100% (93/93), done. +remote: Total 8694 (delta 36), reused 69 (delta 19), pack-reused 8578 +Receiving objects: 100% (8694/8694), 2.60 MiB | 4.59 MiB/s, done. +Resolving deltas: 100% (6339/6339), done. +``` + +### Compile and run tests + +Now compile the `oid` binary: + +``` +$ cd object-introspection/ +$ cmake -G Ninja -B build/ -DWITH_TESTS=On + +$ cmake --build build/ -j + +``` +``` +$ ~/object-introspection# ls -l build/oid +-rwxr-xr-x 1 root root 86818272 Dec 16 18:08 build/oid +``` + +OI needs a system specific configuration file generating so do that now using the `tools/config_gen.py` script: + +``` +$ ~/object-introspection# tools/config_gen.py -c clang++-12 build/oid-cfg.toml +$ ls -l build/oid-cfg.toml +-rw-r--r-- 1 root root 2978 Dec 16 18:18 build/oid-cfg.toml +``` + +You're now ready to introspect objects! diff --git a/website/docs/intro.md b/website/docs/intro.md new file mode 100644 index 0000000..e3ec76f --- /dev/null +++ b/website/docs/intro.md @@ -0,0 +1,23 @@ +--- +sidebar_position: 1 +--- + +# Introduction + +Object Introspection (shortened to ***OI*** and pronounced as in ***boy***) is a memory profiling technology for C++ objects. It provides the ability to dynamically instrument applications to capture the precise memory occupancy of entire object hierarchies including all containers and dynamic allocations. All this with no code modification or recompilation! + +## How does it do this? + +In lieu of more detailed documentation (outside of the code obviously!) here is a brief description of the OI technology. There is a core technology and two different flavors of how it can actually be consumed: a hands-off classic debugger style technology that controls a target process called oid and an API called OIL that provides programmatic object introspection. + +***Type reconstruction:*** A C++ object is described in detail by the debug data generated generated by a compiler (the -g flag with the clang and gcc compiler toolchains) and OI consumes DWARF debug data to reconstruct types from a generated binary. Given a known root type we fully reconstruct the entire type hierarchy including all the descendant types - think of an object as a tree of types that is rooted at a known base type. + +***Code Generation:*** We then iterate over the type tree to auto-generate C++ code to perform operations on each node on the tree. For example, if we have a std::vector of std::string objects then we know that a vector has an iterator and a strings dynamic size can be measured with it's size() method (individual containers have space optimization schemes such as Short String Optimization for strings which we take into account). + +***JIT Compilation:*** The auto-generated C++ code is then JIT compiled into x86-64 object code. Depending upon how we are using OI technology this object code is then relocated for the address space of a target process (oid) or it is ready to be executed directly (OIL). + +***Dynamic Instrumentation:*** The generated object code is copied into a dedicated text segment in the target process ready for execution. Standard text modification techniques are employed to hijack threads at specific points in program execution - these locations are specified by the user. The hijacked thread is then redirected to execute the object code for a specific object. Introspection results are written to a dedicated text segment in the target process during execution of the introspection code. + +***Object Processing:*** Data generated from the object introspection process is then copied out of the target address space by the oid debugger and processed. This includes reconstructing the entire object tree for the captured data and attributing results correctly. + +The above is a very high level view of the basic implementation but there are many more aspects which we hope you'll be interested in. Please bear in mind that this is very much a work-in-progress and the initial design and implementation fits the specific needs of Meta. Our experience is that OI has opened up many new and exciting ways of viewing our application memory footprints in live applications and we hope it can do the same for you. However, please check out the current [constraints](constraints.md) to see whether you can make use of OI currently and please let us know of your requirements if not (please feel free to contribute!). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 0000000..cabc7d5 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: 'Object Introspection', + tagline: 'Dynamic C++ Object Profiling', + url: 'https://your-docusaurus-test-site.com', + baseUrl: '/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + favicon: 'img/favicon.ico', + trailingSlash: false, + + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. + organizationName: 'facebookexperimental', // Usually your GitHub org/user name. + projectName: 'object-introspection', // Usually your repo name. + deploymentBranch: 'web', + + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + sidebarPath: require.resolve('./sidebars.js'), + // Please change this to your repo. + // Remove this to remove the "edit this page" links. + editUrl: + 'https://github.com/facebookexperimental/object-introspection', + }, + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + navbar: { + logo: { + alt: 'OI Logo', + src: 'img/OIBrandmark.svg', + }, + items: [ + // Please keep GitHub link to the right for consistency. + { + href: 'https://github.com/facebookexperimental/object-introspection', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Links', + items: [ + { + label: 'Getting Started', + to: 'docs/intro', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Matrix', + href: 'https://matrix.to/#/#object-introspection:matrix.org', + }, + { + label: 'IRC', + href: 'irc://irc.oftc.net/#object-introspection', + }, + ], + }, + { + title: 'Code', + items: [ + { + label: 'GitHub', + href: 'https://github.com/facebook/docusaurus', + }, + ], + }, + { + title: 'Legal', + // Please do not remove the privacy and terms, it's a legal requirement. + items: [ + { + label: 'Privacy', + href: 'https://opensource.fb.com/legal/privacy/', + }, + { + label: 'Terms', + href: 'https://opensource.fb.com/legal/terms/', + }, + ], + }, + ], + logo: { + alt: 'Meta Open Source Logo', + // This default includes a positive & negative version, allowing for + // appropriate use depending on your site's style. + src: '/img/meta_opensource_logo_negative.svg', + href: 'https://opensource.fb.com', + }, + // Please do not remove the credits, help to publicize Docusaurus :) + copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc. Built with Docusaurus.`, + }, + }), +}; + +module.exports = config; diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..4308bbc --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,15269 @@ +{ + "name": "oi-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "oi-web", + "version": "0.0.0", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/preset-classic": "2.2.0", + "@mdx-js/react": "^1.6.22", + "clsx": "^1.2.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.18.2", + "eslint": "^8.19.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.6.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^2.7.1", + "stylelint": "^14.9.1" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz", + "integrity": "sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==", + "dependencies": { + "@algolia/autocomplete-shared": "1.7.2" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz", + "integrity": "sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==", + "dependencies": { + "@algolia/autocomplete-shared": "1.7.2" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz", + "integrity": "sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==" + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.2.tgz", + "integrity": "sha512-FRweBkK/ywO+GKYfAWbrepewQsPTIEirhi1BdykX9mxvBPtGNKccYAxvGdDCumU1jL4r3cayio4psfzKMejBlA==", + "dependencies": { + "@algolia/cache-common": "4.14.2" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.14.2.tgz", + "integrity": "sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg==" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.14.2.tgz", + "integrity": "sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ==", + "dependencies": { + "@algolia/cache-common": "4.14.2" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.14.2.tgz", + "integrity": "sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.14.2.tgz", + "integrity": "sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.14.2.tgz", + "integrity": "sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q==", + "dependencies": { + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.14.2.tgz", + "integrity": "sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.14.2.tgz", + "integrity": "sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + }, + "node_modules/@algolia/logger-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.14.2.tgz", + "integrity": "sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA==" + }, + "node_modules/@algolia/logger-console": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.14.2.tgz", + "integrity": "sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g==", + "dependencies": { + "@algolia/logger-common": "4.14.2" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.2.tgz", + "integrity": "sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw==", + "dependencies": { + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.14.2.tgz", + "integrity": "sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg==" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.14.2.tgz", + "integrity": "sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg==", + "dependencies": { + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.14.2.tgz", + "integrity": "sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ==", + "dependencies": { + "@algolia/cache-common": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", + "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dependencies": { + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "dependencies": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz", + "integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz", + "integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz", + "integrity": "sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz", + "integrity": "sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.2", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", + "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", + "dependencies": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@docsearch/css": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.0.tgz", + "integrity": "sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg==" + }, + "node_modules/@docsearch/react": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.0.tgz", + "integrity": "sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==", + "dependencies": { + "@algolia/autocomplete-core": "1.7.2", + "@algolia/autocomplete-preset-algolia": "1.7.2", + "@docsearch/css": "3.3.0", + "algoliasearch": "^4.0.0" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.2.0.tgz", + "integrity": "sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==", + "dependencies": { + "@babel/core": "^7.18.6", + "@babel/generator": "^7.18.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.18.6", + "@babel/preset-env": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "@babel/runtime": "^7.18.6", + "@babel/runtime-corejs3": "^7.18.6", + "@babel/traverse": "^7.18.8", + "@docusaurus/cssnano-preset": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/react-loadable": "5.5.2", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "@slorber/static-site-generator-webpack-plugin": "^4.0.7", + "@svgr/webpack": "^6.2.1", + "autoprefixer": "^10.4.7", + "babel-loader": "^8.2.5", + "babel-plugin-dynamic-import-node": "^2.3.3", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "clean-css": "^5.3.0", + "cli-table3": "^0.6.2", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "copy-webpack-plugin": "^11.0.0", + "core-js": "^3.23.3", + "css-loader": "^6.7.1", + "css-minimizer-webpack-plugin": "^4.0.0", + "cssnano": "^5.1.12", + "del": "^6.1.1", + "detect-port": "^1.3.0", + "escape-html": "^1.0.3", + "eta": "^1.12.3", + "file-loader": "^6.2.0", + "fs-extra": "^10.1.0", + "html-minifier-terser": "^6.1.0", + "html-tags": "^3.2.0", + "html-webpack-plugin": "^5.5.0", + "import-fresh": "^3.3.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.6.1", + "postcss": "^8.4.14", + "postcss-loader": "^7.0.0", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "^1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.3", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.3", + "rtl-detect": "^1.0.4", + "semver": "^7.3.7", + "serve-handler": "^6.1.3", + "shelljs": "^0.8.5", + "terser-webpack-plugin": "^5.3.3", + "tslib": "^2.4.0", + "update-notifier": "^5.1.0", + "url-loader": "^4.1.1", + "wait-on": "^6.0.1", + "webpack": "^5.73.0", + "webpack-bundle-analyzer": "^4.5.0", + "webpack-dev-server": "^4.9.3", + "webpack-merge": "^5.8.0", + "webpackbar": "^5.0.2" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@docusaurus/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@docusaurus/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@docusaurus/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@docusaurus/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/core/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@docusaurus/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz", + "integrity": "sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==", + "dependencies": { + "cssnano-preset-advanced": "^5.3.8", + "postcss": "^8.4.14", + "postcss-sort-media-queries": "^4.2.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@docusaurus/logger": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.2.0.tgz", + "integrity": "sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@docusaurus/logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@docusaurus/logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@docusaurus/logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@docusaurus/logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@docusaurus/logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz", + "integrity": "sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==", + "dependencies": { + "@babel/parser": "^7.18.8", + "@babel/traverse": "^7.18.8", + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@mdx-js/mdx": "^1.6.22", + "escape-html": "^1.0.3", + "file-loader": "^6.2.0", + "fs-extra": "^10.1.0", + "image-size": "^1.0.1", + "mdast-util-to-string": "^2.0.0", + "remark-emoji": "^2.2.0", + "stringify-object": "^3.3.0", + "tslib": "^2.4.0", + "unified": "^9.2.2", + "unist-util-visit": "^2.0.3", + "url-loader": "^4.1.1", + "webpack": "^5.73.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.2.0.tgz", + "integrity": "sha512-wDGW4IHKoOr9YuJgy7uYuKWrDrSpsUSDHLZnWQYM9fN7D5EpSmYHjFruUpKWVyxLpD/Wh0rW8hYZwdjJIQUQCQ==", + "dependencies": { + "@docusaurus/react-loadable": "5.5.2", + "@docusaurus/types": "2.2.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@5.5.2" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz", + "integrity": "sha512-0mWBinEh0a5J2+8ZJXJXbrCk1tSTNf7Nm4tYAl5h2/xx+PvH/Bnu0V+7mMljYm/1QlDYALNIIaT/JcoZQFUN3w==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "cheerio": "^1.0.0-rc.12", + "feed": "^4.2.2", + "fs-extra": "^10.1.0", + "lodash": "^4.17.21", + "reading-time": "^1.5.0", + "tslib": "^2.4.0", + "unist-util-visit": "^2.0.3", + "utility-types": "^3.10.0", + "webpack": "^5.73.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.2.0.tgz", + "integrity": "sha512-BOazBR0XjzsHE+2K1wpNxz5QZmrJgmm3+0Re0EVPYFGW8qndCWGNtXW/0lGKhecVPML8yyFeAmnUCIs7xM2wPw==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "@types/react-router-config": "^5.0.6", + "combine-promises": "^1.1.0", + "fs-extra": "^10.1.0", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.4.0", + "utility-types": "^3.10.0", + "webpack": "^5.73.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.2.0.tgz", + "integrity": "sha512-+OTK3FQHk5WMvdelz8v19PbEbx+CNT6VSpx7nVOvMNs5yJCKvmqBJBQ2ZSxROxhVDYn+CZOlmyrC56NSXzHf6g==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "fs-extra": "^10.1.0", + "tslib": "^2.4.0", + "webpack": "^5.73.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.2.0.tgz", + "integrity": "sha512-p9vOep8+7OVl6r/NREEYxf4HMAjV8JMYJ7Bos5fCFO0Wyi9AZEo0sCTliRd7R8+dlJXZEgcngSdxAUo/Q+CJow==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "fs-extra": "^10.1.0", + "react-json-view": "^1.21.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.2.0.tgz", + "integrity": "sha512-+eZVVxVeEnV5nVQJdey9ZsfyEVMls6VyWTIj8SmX0k5EbqGvnIfET+J2pYEuKQnDIHxy+syRMoRM6AHXdHYGIg==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.2.0.tgz", + "integrity": "sha512-6SOgczP/dYdkqUMGTRqgxAS1eTp6MnJDAQMy8VCF1QKbWZmlkx4agHDexihqmYyCujTYHqDAhm1hV26EET54NQ==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.2.0.tgz", + "integrity": "sha512-0jAmyRDN/aI265CbWZNZuQpFqiZuo+5otk2MylU9iVrz/4J7gSc+ZJ9cy4EHrEsW7PV8s1w18hIEsmcA1YgkKg==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "fs-extra": "^10.1.0", + "sitemap": "^7.1.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.2.0.tgz", + "integrity": "sha512-yKIWPGNx7BT8v2wjFIWvYrS+nvN04W+UameSFf8lEiJk6pss0kL6SG2MRvyULiI3BDxH+tj6qe02ncpSPGwumg==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/plugin-debug": "2.2.0", + "@docusaurus/plugin-google-analytics": "2.2.0", + "@docusaurus/plugin-google-gtag": "2.2.0", + "@docusaurus/plugin-sitemap": "2.2.0", + "@docusaurus/theme-classic": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-search-algolia": "2.2.0", + "@docusaurus/types": "2.2.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/react-loadable": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", + "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "dependencies": { + "@types/react": "*", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.2.0.tgz", + "integrity": "sha512-kjbg/qJPwZ6H1CU/i9d4l/LcFgnuzeiGgMQlt6yPqKo0SOJIBMPuz7Rnu3r/WWbZFPi//o8acclacOzmXdUUEg==", + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/types": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-common": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "@mdx-js/react": "^1.6.22", + "clsx": "^1.2.1", + "copy-text-to-clipboard": "^3.0.1", + "infima": "0.2.0-alpha.42", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.4.14", + "prism-react-renderer": "^1.3.5", + "prismjs": "^1.28.0", + "react-router-dom": "^5.3.3", + "rtlcss": "^3.5.0", + "tslib": "^2.4.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.2.0.tgz", + "integrity": "sha512-R8BnDjYoN90DCL75gP7qYQfSjyitXuP9TdzgsKDmSFPNyrdE3twtPNa2dIN+h+p/pr+PagfxwWbd6dn722A1Dw==", + "dependencies": { + "@docusaurus/mdx-loader": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/plugin-content-blog": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/plugin-content-pages": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^1.2.1", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^1.3.5", + "tslib": "^2.4.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.2.0.tgz", + "integrity": "sha512-2h38B0tqlxgR2FZ9LpAkGrpDWVdXZ7vltfmTdX+4RsDs3A7khiNsmZB+x/x6sA4+G2V2CvrsPMlsYBy5X+cY1w==", + "dependencies": { + "@docsearch/react": "^3.1.1", + "@docusaurus/core": "2.2.0", + "@docusaurus/logger": "2.2.0", + "@docusaurus/plugin-content-docs": "2.2.0", + "@docusaurus/theme-common": "2.2.0", + "@docusaurus/theme-translations": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@docusaurus/utils-validation": "2.2.0", + "algoliasearch": "^4.13.1", + "algoliasearch-helper": "^3.10.0", + "clsx": "^1.2.1", + "eta": "^1.12.3", + "fs-extra": "^10.1.0", + "lodash": "^4.17.21", + "tslib": "^2.4.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.2.0.tgz", + "integrity": "sha512-3T140AG11OjJrtKlY4pMZ5BzbGRDjNs2co5hJ6uYJG1bVWlhcaFGqkaZ5lCgKflaNHD7UHBHU9Ec5f69jTdd6w==", + "dependencies": { + "fs-extra": "^10.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@docusaurus/types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz", + "integrity": "sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.6.0", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.73.0", + "webpack-merge": "^5.8.0" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz", + "integrity": "sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==", + "dependencies": { + "@docusaurus/logger": "2.2.0", + "@svgr/webpack": "^6.2.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.1.0", + "github-slugger": "^1.4.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "resolve-pathname": "^3.0.0", + "shelljs": "^0.8.5", + "tslib": "^2.4.0", + "url-loader": "^4.1.1", + "webpack": "^5.73.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.2.0.tgz", + "integrity": "sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz", + "integrity": "sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==", + "dependencies": { + "@docusaurus/logger": "2.2.0", + "@docusaurus/utils": "2.2.0", + "joi": "^17.6.0", + "js-yaml": "^4.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "devOptional": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "devOptional": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "devOptional": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "devOptional": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "devOptional": true + }, + "node_modules/@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", + "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "dependencies": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "node_modules/@mdx-js/mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", + "integrity": "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==", + "dependencies": { + "@babel/core": "7.12.9", + "@babel/plugin-syntax-jsx": "7.12.1", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "1.6.22", + "babel-plugin-apply-mdx-type-prop": "1.6.22", + "babel-plugin-extract-import-names": "1.6.22", + "camelcase-css": "2.0.1", + "detab": "2.0.4", + "hast-util-raw": "6.0.1", + "lodash.uniq": "4.5.0", + "mdast-util-to-hast": "10.0.1", + "remark-footnotes": "2.0.0", + "remark-mdx": "1.6.22", + "remark-parse": "8.0.3", + "remark-squeeze-paragraphs": "4.0.0", + "style-to-object": "0.3.0", + "unified": "9.2.0", + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@mdx-js/mdx/node_modules/@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@mdx-js/mdx/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mdx-js/mdx/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@mdx-js/mdx/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@mdx-js/mdx/node_modules/unified": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", + "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + } + }, + "node_modules/@mdx-js/util": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", + "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==" + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@slorber/static-site-generator-webpack-plugin": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz", + "integrity": "sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==", + "dependencies": { + "eval": "^0.1.8", + "p-map": "^4.0.0", + "webpack-sources": "^3.2.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", + "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz", + "integrity": "sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz", + "integrity": "sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", + "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", + "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", + "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", + "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", + "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", + "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", + "@svgr/babel-plugin-remove-jsx-attribute": "*", + "@svgr/babel-plugin-remove-jsx-empty-expression": "*", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", + "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", + "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", + "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", + "@svgr/babel-plugin-transform-svg-component": "^6.5.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", + "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "dependencies": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/plugin-jsx": "^6.5.1", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", + "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", + "dependencies": { + "@babel/types": "^7.20.0", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", + "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", + "dependencies": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/hast-util-to-babel-ast": "^6.5.1", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "^6.0.0" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz", + "integrity": "sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==", + "dependencies": { + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "svgo": "^2.8.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.5.1.tgz", + "integrity": "sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==", + "dependencies": { + "@babel/core": "^7.19.6", + "@babel/plugin-transform-react-constant-elements": "^7.18.12", + "@babel/preset-env": "^7.19.4", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "@svgr/core": "^6.5.1", + "@svgr/plugin-jsx": "^6.5.1", + "@svgr/plugin-svgo": "^6.5.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.4.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", + "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.12.tgz", + "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", + "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.6.tgz", + "integrity": "sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", + "integrity": "sha512-72bWxFKTK6uwWJAVT+3rF6Jo6RTojiJ27FQo8Rf60AL+VZbzoVPnMFhKsUnbjR8A3BTCYQ7Mv3hnl8T0A+CX9g==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "devOptional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", + "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/algoliasearch": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.14.2.tgz", + "integrity": "sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.14.2", + "@algolia/cache-common": "4.14.2", + "@algolia/cache-in-memory": "4.14.2", + "@algolia/client-account": "4.14.2", + "@algolia/client-analytics": "4.14.2", + "@algolia/client-common": "4.14.2", + "@algolia/client-personalization": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/logger-console": "4.14.2", + "@algolia/requester-browser-xhr": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/requester-node-http": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz", + "integrity": "sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw==", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axe-core": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", + "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-plugin-apply-mdx-type-prop": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz", + "integrity": "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==", + "dependencies": { + "@babel/helper-plugin-utils": "7.10.4", + "@mdx-js/util": "1.6.22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@babel/core": "^7.11.6" + } + }, + "node_modules/babel-plugin-apply-mdx-type-prop/node_modules/@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-extract-import-names": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz", + "integrity": "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==", + "dependencies": { + "@babel/helper-plugin-utils": "7.10.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/babel-plugin-extract-import-names/node_modules/@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001439", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", + "integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", + "integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", + "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, + "node_modules/combine-promises": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.1.0.tgz", + "integrity": "sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/copy-text-to-clipboard": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz", + "integrity": "sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", + "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dependencies": { + "browserslist": "^4.21.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", + "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", + "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, + "node_modules/css-loader": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", + "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.18", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", + "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", + "dependencies": { + "cssnano": "^5.1.8", + "jest-worker": "^29.1.2", + "postcss": "^8.4.17", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz", + "integrity": "sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==", + "dependencies": { + "cssnano-preset-default": "^5.2.13", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.9.tgz", + "integrity": "sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg==", + "dependencies": { + "autoprefixer": "^10.4.12", + "cssnano-preset-default": "^5.2.13", + "postcss-discard-unused": "^5.1.0", + "postcss-merge-idents": "^5.1.1", + "postcss-reduce-idents": "^5.2.0", + "postcss-zindex": "^5.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", + "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.3", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.1", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "devOptional": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", + "integrity": "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==", + "dependencies": { + "repeat-string": "^1.5.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "devOptional": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", + "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "devOptional": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-header": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", + "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7.7.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.31.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", + "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "devOptional": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "devOptional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "devOptional": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "devOptional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "devOptional": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "devOptional": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "devOptional": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "devOptional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "devOptional": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz", + "integrity": "sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "devOptional": true + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fbemitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", + "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", + "dependencies": { + "fbjs": "^3.0.0" + } + }, + "node_modules/fbjs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz", + "integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.30" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "devOptional": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "devOptional": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "devOptional": true + }, + "node_modules/flux": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.3.tgz", + "integrity": "sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==", + "dependencies": { + "fbemitter": "^3.0.0", + "fbjs": "^3.0.1" + }, + "peerDependencies": { + "react": "^15.0.2 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", + "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "devOptional": true + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-to-hyperscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", + "integrity": "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==", + "dependencies": { + "@types/unist": "^2.0.3", + "comma-separated-tokens": "^1.0.0", + "property-information": "^5.3.0", + "space-separated-tokens": "^1.0.0", + "style-to-object": "^0.3.0", + "unist-util-is": "^4.0.0", + "web-namespaces": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", + "integrity": "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==", + "dependencies": { + "@types/parse5": "^5.0.0", + "hastscript": "^6.0.0", + "property-information": "^5.0.0", + "vfile": "^4.0.0", + "vfile-location": "^3.2.0", + "web-namespaces": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", + "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^6.0.0", + "hast-util-to-parse5": "^6.0.0", + "html-void-elements": "^1.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^3.0.0", + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/hast-util-to-parse5": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", + "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", + "dependencies": { + "hast-to-hyperscript": "^9.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/immer": { + "version": "9.0.16", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", + "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.42", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.42.tgz", + "integrity": "sha512-ift8OXNbQQwtbIt6z16KnSWP7uJ/SysSMFI4F87MNRTicypfl4Pv3E2OGVv6N3nSZFJvA8imYulCBS64iyHYww==", + "engines": { + "node": ">=12" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-ci/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-util": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", + "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "dependencies": { + "@jest/types": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "devOptional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "devOptional": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/known-css-properties": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "dev": true + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.6.tgz", + "integrity": "sha512-HNkaCgM8wZgE/BZACeotAAgpL9FUjEnhgF0FVQMIgH//zqTPreLYMb3rWYkYAqPoF75Jwuycp1da7uz66cfFQg==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "devOptional": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "devOptional": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", + "dependencies": { + "unist-util-remove": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz", + "integrity": "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-definitions": "^4.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz", + "integrity": "sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==", + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "devOptional": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "devOptional": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.4.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", + "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-unused": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz", + "integrity": "sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-merge-idents": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz", + "integrity": "sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", + "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz", + "integrity": "sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", + "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.3.0.tgz", + "integrity": "sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg==", + "dependencies": { + "sort-css-media-queries": "2.1.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.16" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/postcss-zindex": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.1.0.tgz", + "integrity": "sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "devOptional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz", + "integrity": "sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==", + "peerDependencies": { + "react": ">=0.14.9" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==", + "dependencies": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-dev-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/react-dev-utils/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "node_modules/react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, + "node_modules/react-helmet-async": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-json-view": { + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", + "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", + "dependencies": { + "flux": "^4.0.1", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^8.3.2" + }, + "peerDependencies": { + "react": "^17.0.0 || ^16.3.0 || ^15.5.4", + "react-dom": "^17.0.0 || ^16.3.0 || ^15.5.4" + } + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", + "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "dependencies": { + "@types/react": "*", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", + "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz", + "integrity": "sha512-YrTFaEHLgJsi8sJVYHBzYn+mkP3prGkmP2DKb/tm0t7CLJY5t1Rxix8070LAKb0wby7bl/lf2EeHkuMihMZMwQ==", + "dependencies": { + "@babel/runtime": "^7.10.2", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "devOptional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.2.0.tgz", + "integrity": "sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==", + "dependencies": { + "emoticon": "^3.2.0", + "node-emoji": "^1.10.0", + "unist-util-visit": "^2.0.3" + } + }, + "node_modules/remark-footnotes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", + "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", + "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", + "dependencies": { + "@babel/core": "7.12.9", + "@babel/helper-plugin-utils": "7.10.4", + "@babel/plugin-proposal-object-rest-spread": "7.12.1", + "@babel/plugin-syntax-jsx": "7.12.1", + "@mdx-js/util": "1.6.22", + "is-alphabetical": "1.0.4", + "remark-parse": "8.0.3", + "unified": "9.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx/node_modules/@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/remark-mdx/node_modules/@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "node_modules/remark-mdx/node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/remark-mdx/node_modules/@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/remark-mdx/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/remark-mdx/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/remark-mdx/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remark-mdx/node_modules/unified": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", + "dependencies": { + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", + "dependencies": { + "mdast-squeeze-paragraphs": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rtl-detect": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.0.4.tgz", + "integrity": "sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==" + }, + "node_modules/rtlcss": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz", + "integrity": "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==", + "dependencies": { + "find-up": "^5.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.3.11", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.6.0.tgz", + "integrity": "sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", + "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/sitemap": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", + "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz", + "integrity": "sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, + "node_modules/state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/stylelint": { + "version": "14.16.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.0.tgz", + "integrity": "sha512-X6uTi9DcxjzLV8ZUAjit1vsRtSwcls0nl07c9rqOPzvpA8IvTX/xWEkBRowS0ffevRrqkHa/ThDEu86u73FQDg==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.26.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", + "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==" + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "devOptional": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dependencies": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unist-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", + "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", + "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", + "dependencies": { + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/update-notifier/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/update-notifier/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/update-notifier/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", + "engines": { + "node": ">=4" + } + }, + "node_modules/update-notifier/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/update-notifier/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", + "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/wait-on": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", + "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "dependencies": { + "axios": "^0.25.0", + "joi": "^17.6.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.5.4" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", + "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", + "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpackbar": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", + "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.3", + "pretty-time": "^1.1.0", + "std-env": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "webpack": "3 || 4 || 5" + } + }, + "node_modules/webpackbar/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpackbar/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpackbar/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpackbar/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/webpackbar/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", + "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..1c0081f --- /dev/null +++ b/website/package.json @@ -0,0 +1,56 @@ +{ + "name": "oi-web", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "ci": "yarn lint && yarn format:diff", + "lint": "eslint --cache \"**/*.js\" && stylelint \"**/*.css\"", + "format": "prettier --config .prettierrc --write \"**/*.{js,jsx,ts,tsx,md,mdx}\"", + "format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\"" + }, + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/preset-classic": "2.2.0", + "@mdx-js/react": "^1.6.22", + "clsx": "^1.2.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.18.2", + "eslint": "^8.19.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.6.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^2.7.1", + "stylelint": "^14.9.1" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=16.14" + } +} diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 0000000..c43d699 --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +module.exports = { + mysidebar: [ + 'intro', + 'getting-started', + { + type: 'category', + label: 'A Brief Practical Introduction', + items: ['addrbook-intro', 'addrbook-this', 'addrbook-funcargs'], + }, + 'constraints', + 'contributing', + ], +}; diff --git a/website/src/css/custom.css b/website/src/css/custom.css new file mode 100644 index 0000000..2396bcc --- /dev/null +++ b/website/src/css/custom.css @@ -0,0 +1,37 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; + --ifm-code-font-size: 95%; +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; +} diff --git a/website/src/pages/index.js b/website/src/pages/index.js new file mode 100644 index 0000000..dbbaeda --- /dev/null +++ b/website/src/pages/index.js @@ -0,0 +1,106 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import React from 'react'; +import clsx from 'clsx'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import styles from './styles.module.css'; + +const features = [ + { + title: 'Byte Accurate', + imageUrl: 'img/OISymbol.svg', + description: ( + <> + Object Introspection provides byte accurate memory occupancy information for arbitrarily complex C++ object hierarchies including all static and dynamic memory allocations in an object. + + ), + }, + { + title: 'Live Applications', + imageUrl: 'img/ObjectIntrospection-SecondarySymbol.svg', + description: ( + <> + Introspect objects in live, production applications with no source modification or recompilation. DWARF debug data powers the reconstruction of object types. + + ), + }, + { + title: 'Debugger and API', + imageUrl: 'img/undraw_docusaurus_react.svg', + description: ( + <> + The technology provides a hands-off classical debugger style application for introspecting live applications and an API to provide programmatic interfaces to an object. Use whatever approach fits your needs! + + ), + }, +]; + +function Feature({imageUrl, title, description}) { + const imgUrl = useBaseUrl(imageUrl); + return ( +
+ {imgUrl && ( +
+ {title} +
+ )} +

{title}

+

{description}

+
+ ); +} + +export default function Home() { + const context = useDocusaurusContext(); + const {siteConfig = {}} = context; + return ( + +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+ + Get Started + +
+
+
+
+ {features && features.length > 0 && ( +
+
+
+ {features.map(({title, imageUrl, description}) => ( + + ))} +
+
+
+ )} +
+
+ ); +} diff --git a/website/src/pages/markdown-page.md b/website/src/pages/markdown-page.md new file mode 100644 index 0000000..9756c5b --- /dev/null +++ b/website/src/pages/markdown-page.md @@ -0,0 +1,7 @@ +--- +title: Markdown page example +--- + +# Markdown page example + +You don't need React to write simple standalone pages. diff --git a/website/src/pages/styles.module.css b/website/src/pages/styles.module.css new file mode 100644 index 0000000..e74559a --- /dev/null +++ b/website/src/pages/styles.module.css @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +@media screen and (max-width: 996px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} + +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.featureImage { + height: 200px; + width: 200px; +} diff --git a/website/static/.DS_Store b/website/static/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1de1334fbd568cc43da1e061b27c0443ec625b47 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8O({YS3VK`cTClBxEnY&bFJMFuDm5Xc2GeY5QgbMU-1UWg5ueAI z-3?gGS;Wr3?l-@?*$=Wmj4|#m!yaQcW6Xkv$Wf^fbXSIICK-|A80kESWdPPkFgLNk z4*2bL7O@$d2Ko2zkE1l}c0YNo-q_h~T1~5MJ$lbF_cA|QrmnxZN9#t)I4E^LxQ!S1`Ca9>wpfg&lqnYqJWNX2}EJgF<5AX2ng4ufVz~M zCkEH$;1?#(F<5BS<&3MDVH`7a`FP=KcJK?8&bXtIdSZYW*kqutO&ibuEBIw9ANiXl zWDx_zz&~SvH%8vbg+-aO^~dt?tQF9nprK%1feHxd8(1@d; SUzG#WML-ck9Wn3=4158In@Owy literal 0 HcmV?d00001 diff --git a/website/static/._.DS_Store b/website/static/._.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a5b28df1cbc6e15bd0d35cdadd0c2e65d5131c7d GIT binary patch literal 120 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}u^SMB_!U6R08`;00ODZ-jv*mIP;rnB Iur73U08|YJ=l}o! literal 0 HcmV?d00001 diff --git a/website/static/.nojekyll b/website/static/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/website/static/img/._OIBrandmark.svg b/website/static/img/._OIBrandmark.svg new file mode 100755 index 0000000000000000000000000000000000000000..a9576a7663d628b8877a93b44f5ad0983eeb1472 GIT binary patch literal 724 zcmbV}&2G~`5XYyh0tE>WA}t@MAi<$RcKsE9C>$E6sVbqUv?PdIv{`SQRkKdA-lQ$> zz$;XUJ1+ny#0zlX!rL%TkXUg-jI=ZI{O0fZuNJQD+yDa5rL2DZy8gEQJE|Vs#Nz>w zk67`W{J{DT(dzY1*JG7p<2#-^MU464p!ZPRm+_)_MWD|aFeRzD5ws)_-b^5huwEz|W5%WFAiQQ)C z@rcCTWNxUvq zHom0mzf(GpCweUB)8g`c-amdRx`+9NQd!(NJ;y~(%z_ciMl1}RQ4|GE=tl7{8S=~@ mF;7T*-Mw;|KfjO@$MbwQ44qbcd34}+b88Wc{#o+sP3srm*rIp< literal 0 HcmV?d00001 diff --git a/website/static/img/._OISymbol.svg b/website/static/img/._OISymbol.svg new file mode 100755 index 0000000000000000000000000000000000000000..f51e7c623fb373adabb5d44973abe0cc4162f5de GIT binary patch literal 624 zcma)3T~FIE6um^+*8@xw8;G|e?FCH!uXlc{*T&ALGUOMv&L-_*aOz7DWu z=*DC5keglCjYu!_W0HG?-%GFbOo?979mXl8)TY58wrMgTcHc|-_VA#erh_;bCVm7& z$!qbs)J#^a;tFFj?cfg(Y6qI1-hH|Fad?~c$CunI)JTeNoRZOfSPWSha6j;)t~YTD yLZjFxISHdY;&~i|aUm7{%5J?ZKmAlw*YkW5Qa4GLhX?*~X)hxA^*TNoC;tFeMw89} literal 0 HcmV?d00001 diff --git a/website/static/img/._ObjectIntrospection-SecondarySymbol.svg b/website/static/img/._ObjectIntrospection-SecondarySymbol.svg new file mode 100755 index 0000000000000000000000000000000000000000..193eb6d70ac0d8cd5882a79e23af18e0500a02ed GIT binary patch literal 624 zcma)3U2oGc6upZCg9kQE<3qf4(q5)1S?st?q9R0^rJ_h>(>4k`pvZ|$X01*;C#|;q zE*|+&@DI4zc(8clN2;9%8A;|ZhI(kpqL zYhLq8=B?y9zh`qTbsalwDSaE4`9pElt2#4IY8(EY+ITZ{Z0aB zg76G;+M3ds;C9*NbY=*cbKlaN%Y|vnRqM6fRI>G&mKSsRaGUcP{f_~j+kRXBhWakR zYfHBti-%kv`F=!(gCHg){T>X{OS8~&n1tgvWsJG3pT#aqGUASCGIGbqqcqLp{x}ID zAX;6?ca`C)=C#lmlN%3zfCQ~S8{cv9b)AU`C#N$uot&tTQ9(v&o}GUX1&azsgMkeF xe#uK3L`2Y#g&`dXL5b4%D@V<`I=s \ No newline at end of file diff --git a/website/static/img/OISymbol.svg b/website/static/img/OISymbol.svg new file mode 100755 index 0000000..1259810 --- /dev/null +++ b/website/static/img/OISymbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/static/img/ObjectIntrospection-SecondarySymbol.svg b/website/static/img/ObjectIntrospection-SecondarySymbol.svg new file mode 100755 index 0000000..ffb6507 --- /dev/null +++ b/website/static/img/ObjectIntrospection-SecondarySymbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/static/img/docusaurus.png b/website/static/img/docusaurus.png new file mode 100644 index 0000000000000000000000000000000000000000..f458149e3c8f53335f28fbc162ae67f55575c881 GIT binary patch literal 5142 zcma)=cTf{R(}xj7f`AaDml%oxrAm_`5IRVc-jPtHML-0kDIiip57LWD@4bW~(nB|) z34|^sbOZqj<;8ct`Tl-)=Jw`pZtiw=e$UR_Mn2b8rM$y@hlq%XQe90+?|Mf68-Ux_ zzTBiDn~3P%oVt>{f$z+YC7A)8ak`PktoIXDkpXod+*gQW4fxTWh!EyR9`L|fi4YlH z{IyM;2-~t3s~J-KF~r-Z)FWquQCfG*TQy6w*9#k2zUWV-+tCNvjrtl9(o}V>-)N!) ziZgEgV>EG+b(j@ex!dx5@@nGZim*UfFe<+e;(xL|j-Pxg(PCsTL~f^br)4{n5?OU@ z*pjt{4tG{qBcDSa3;yKlopENd6Yth=+h9)*lkjQ0NwgOOP+5Xf?SEh$x6@l@ZoHoYGc5~d2>pO43s3R|*yZw9yX^kEyUV2Zw1%J4o`X!BX>CwJ zI8rh1-NLH^x1LnaPGki_t#4PEz$ad+hO^$MZ2 ziwt&AR}7_yq-9Pfn}k3`k~dKCbOsHjvWjnLsP1{)rzE8ERxayy?~{Qz zHneZ2gWT3P|H)fmp>vA78a{0&2kk3H1j|n59y{z@$?jmk9yptqCO%* zD2!3GHNEgPX=&Ibw?oU1>RSxw3;hhbOV77-BiL%qQb1(4J|k=Y{dani#g>=Mr?Uyd z)1v~ZXO_LT-*RcG%;i|Wy)MvnBrshlQoPxoO*82pKnFSGNKWrb?$S$4x+24tUdpb= zr$c3K25wQNUku5VG@A=`$K7%?N*K+NUJ(%%)m0Vhwis*iokN#atyu(BbK?+J+=H z!kaHkFGk+qz`uVgAc600d#i}WSs|mtlkuwPvFp) z1{Z%nt|NwDEKj1(dhQ}GRvIj4W?ipD76jZI!PGjd&~AXwLK*98QMwN&+dQN1ML(6< z@+{1`=aIc z9Buqm97vy3RML|NsM@A>Nw2=sY_3Ckk|s;tdn>rf-@Ke1m!%F(9(3>V%L?w#O&>yn z(*VIm;%bgezYB;xRq4?rY})aTRm>+RL&*%2-B%m; zLtxLTBS=G!bC$q;FQ|K3{nrj1fUp`43Qs&V!b%rTVfxlDGsIt3}n4p;1%Llj5ePpI^R} zl$Jhx@E}aetLO!;q+JH@hmelqg-f}8U=XnQ+~$9RHGUDOoR*fR{io*)KtYig%OR|08ygwX%UqtW81b@z0*`csGluzh_lBP=ls#1bwW4^BTl)hd|IIfa zhg|*M%$yt@AP{JD8y!7kCtTmu{`YWw7T1}Xlr;YJTU1mOdaAMD172T8Mw#UaJa1>V zQ6CD0wy9NEwUsor-+y)yc|Vv|H^WENyoa^fWWX zwJz@xTHtfdhF5>*T70(VFGX#8DU<^Z4Gez7vn&4E<1=rdNb_pj@0?Qz?}k;I6qz@| zYdWfcA4tmI@bL5JcXuoOWp?ROVe*&o-T!><4Ie9@ypDc!^X&41u(dFc$K$;Tv$c*o zT1#8mGWI8xj|Hq+)#h5JToW#jXJ73cpG-UE^tsRf4gKw>&%Z9A>q8eFGC zG@Iv(?40^HFuC_-%@u`HLx@*ReU5KC9NZ)bkS|ZWVy|_{BOnlK)(Gc+eYiFpMX>!# zG08xle)tntYZ9b!J8|4H&jaV3oO(-iFqB=d}hGKk0 z%j)johTZhTBE|B-xdinS&8MD=XE2ktMUX8z#eaqyU?jL~PXEKv!^) zeJ~h#R{@O93#A4KC`8@k8N$T3H8EV^E2 z+FWxb6opZnX-av5ojt@`l3TvSZtYLQqjps{v;ig5fDo^}{VP=L0|uiRB@4ww$Eh!CC;75L%7|4}xN+E)3K&^qwJizphcnn=#f<&Np$`Ny%S)1*YJ`#@b_n4q zi%3iZw8(I)Dzp0yY}&?<-`CzYM5Rp+@AZg?cn00DGhf=4|dBF8BO~2`M_My>pGtJwNt4OuQm+dkEVP4 z_f*)ZaG6@t4-!}fViGNd%E|2%ylnzr#x@C!CrZSitkHQ}?_;BKAIk|uW4Zv?_npjk z*f)ztC$Cj6O<_{K=dPwO)Z{I=o9z*lp?~wmeTTP^DMP*=<-CS z2FjPA5KC!wh2A)UzD-^v95}^^tT<4DG17#wa^C^Q`@f@=jLL_c3y8@>vXDJd6~KP( zurtqU1^(rnc=f5s($#IxlkpnU=ATr0jW`)TBlF5$sEwHLR_5VPTGiO?rSW9*ND`bYN*OX&?=>!@61{Z4)@E;VI9 zvz%NmR*tl>p-`xSPx$}4YcdRc{_9k)>4Jh&*TSISYu+Y!so!0JaFENVY3l1n*Fe3_ zRyPJ(CaQ-cNP^!3u-X6j&W5|vC1KU!-*8qCcT_rQN^&yqJ{C(T*`(!A=))=n%*-zp_ewRvYQoJBS7b~ zQlpFPqZXKCXUY3RT{%UFB`I-nJcW0M>1^*+v)AxD13~5#kfSkpWys^#*hu)tcd|VW zEbVTi`dbaM&U485c)8QG#2I#E#h)4Dz8zy8CLaq^W#kXdo0LH=ALhK{m_8N@Bj=Um zTmQOO*ID(;Xm}0kk`5nCInvbW9rs0pEw>zlO`ZzIGkB7e1Afs9<0Z(uS2g*BUMhp> z?XdMh^k}k<72>}p`Gxal3y7-QX&L{&Gf6-TKsE35Pv%1 z;bJcxPO+A9rPGsUs=rX(9^vydg2q`rU~otOJ37zb{Z{|)bAS!v3PQ5?l$+LkpGNJq zzXDLcS$vMy|9sIidXq$NE6A-^v@)Gs_x_3wYxF%y*_e{B6FvN-enGst&nq0z8Hl0< z*p6ZXC*su`M{y|Fv(Vih_F|83=)A6ay-v_&ph1Fqqcro{oeu99Y0*FVvRFmbFa@gs zJ*g%Gik{Sb+_zNNf?Qy7PTf@S*dTGt#O%a9WN1KVNj`q$1Qoiwd|y&_v?}bR#>fdP zSlMy2#KzRq4%?ywXh1w;U&=gKH%L~*m-l%D4Cl?*riF2~r*}ic9_{JYMAwcczTE`!Z z^KfriRf|_YcQ4b8NKi?9N7<4;PvvQQ}*4YxemKK3U-7i}ap8{T7=7`e>PN7BG-Ej;Uti2$o=4T#VPb zm1kISgGzj*b?Q^MSiLxj26ypcLY#RmTPp+1>9zDth7O?w9)onA%xqpXoKA-`Jh8cZ zGE(7763S3qHTKNOtXAUA$H;uhGv75UuBkyyD;eZxzIn6;Ye7JpRQ{-6>)ioiXj4Mr zUzfB1KxvI{ZsNj&UA`+|)~n}96q%_xKV~rs?k=#*r*7%Xs^Hm*0~x>VhuOJh<2tcb zKbO9e-w3zbekha5!N@JhQm7;_X+J!|P?WhssrMv5fnQh$v*986uWGGtS}^szWaJ*W z6fLVt?OpPMD+-_(3x8Ra^sX~PT1t5S6bfk@Jb~f-V)jHRul#Hqu;0(+ER7Z(Z4MTR z+iG>bu+BW2SNh|RAGR2-mN5D1sTcb-rLTha*@1@>P~u;|#2N{^AC1hxMQ|(sp3gTa zDO-E8Yn@S7u=a?iZ!&&Qf2KKKk7IT`HjO`U*j1~Df9Uxz$~@otSCK;)lbLSmBuIj% zPl&YEoRwsk$8~Az>>djrdtp`PX z`Pu#IITS7lw07vx>YE<4pQ!&Z^7L?{Uox`CJnGjYLh1XN^tt#zY*0}tA*a=V)rf=&-kLgD|;t1D|ORVY}8 F{0H{b<4^zq literal 0 HcmV?d00001 diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c01d54bcd39a5f853428f3cd5aa0f383d963c484 GIT binary patch literal 3626 zcmb`Je@s(X6vrR`EK3%b%orErlDW({vnABqA zcfaS{d+xbU5JKp0*;0YOg+;Fl!eT)XRuapIwFLL`=imZCSon$`se`_<%@MB=M~KG+ z=EW^FL`w|Bo>*ktlaS^(fut!95`iG5u=SZ8nfDHO#GaTlH1-XG^;vsjUb^gWTVz0+ z^=WR1wv9-2oeR=_;fL0H7rNWqAzGtO(D;`~cX(RcN0w2v24Y8)6t`cS^_ghs`_ho? z{0ka~1Dgo8TfAP$r*ua?>$_V+kZ!-(TvEJ7O2f;Y#tezt$&R4 zLI}=-y@Z!grf*h3>}DUL{km4R>ya_I5Ag#{h_&?+HpKS!;$x3LC#CqUQ8&nM?X))Q zXAy2?`YL4FbC5CgJu(M&Q|>1st8XXLZ|5MgwgjP$m_2Vt0(J z&Gu7bOlkbGzGm2sh?X`){7w69Y$1#@P@7DF{ZE=4%T0NDS)iH`tiPSKpDNW)zmtn( zw;4$f>k)4$LBc>eBAaTZeCM2(iD+sHlj!qd z2GjRJ>f_Qes(+mnzdA^NH?^NB(^o-%Gmg$c8MNMq&`vm@9Ut;*&$xSD)PKH{wBCEC z4P9%NQ;n2s59ffMn8*5)5AAg4-93gBXBDX`A7S& zH-|%S3Wd%T79fk-e&l`{!?lve8_epXhE{d3Hn$Cg!t=-4D(t$cK~7f&4s?t7wr3ZP z*!SRQ-+tr|e1|hbc__J`k3S!rMy<0PHy&R`v#aJv?`Y?2{avK5sQz%=Us()jcNuZV z*$>auD4cEw>;t`+m>h?f?%VFJZj8D|Y1e_SjxG%J4{-AkFtT2+ZZS5UScS~%;dp!V>)7zi`w(xwSd*FS;Lml=f6hn#jq)2is4nkp+aTrV?)F6N z>DY#SU0IZ;*?Hu%tSj4edd~kYNHMFvS&5}#3-M;mBCOCZL3&;2obdG?qZ>rD|zC|Lu|sny76pn2xl|6sk~Hs{X9{8iBW zwiwgQt+@hi`FYMEhX2 \ No newline at end of file diff --git a/website/static/img/meta_opensource_logo.svg b/website/static/img/meta_opensource_logo.svg new file mode 100644 index 0000000..83a2175 --- /dev/null +++ b/website/static/img/meta_opensource_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/static/img/meta_opensource_logo_negative.svg b/website/static/img/meta_opensource_logo_negative.svg new file mode 100644 index 0000000..b36a423 --- /dev/null +++ b/website/static/img/meta_opensource_logo_negative.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/static/img/undraw_docusaurus_mountain.svg b/website/static/img/undraw_docusaurus_mountain.svg new file mode 100644 index 0000000..af961c4 --- /dev/null +++ b/website/static/img/undraw_docusaurus_mountain.svg @@ -0,0 +1,171 @@ + + Easy to Use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/static/img/undraw_docusaurus_react.svg b/website/static/img/undraw_docusaurus_react.svg new file mode 100644 index 0000000..94b5cf0 --- /dev/null +++ b/website/static/img/undraw_docusaurus_react.svg @@ -0,0 +1,170 @@ + + Powered by React + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/static/img/undraw_docusaurus_tree.svg b/website/static/img/undraw_docusaurus_tree.svg new file mode 100644 index 0000000..d9161d3 --- /dev/null +++ b/website/static/img/undraw_docusaurus_tree.svg @@ -0,0 +1,40 @@ + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +